ArgumentNullException - 如何简化?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArgumentNullException - 如何简化?相关的知识,希望对你有一定的参考价值。
我注意到这个代码在我的构造函数中出现了很多:
if (someParam == null) throw new ArgumentNullException("someParam");
if (someOtherParam == null) throw new ArgumentNullException("someOtherParam");
...
我有一些构造函数,其中注入了几个东西,并且必须都是非null。谁能想到一种简化这种方法的方法?我唯一能想到的是以下几点:
public static class ExceptionHelpers
{
public static void CheckAndThrowArgNullEx(IEnumerable<KeyValuePair<string, object>> parameters)
{
foreach(var parameter in parameters)
if(parameter.Value == null) throw new ArgumentNullException(parameter.Key);
}
}
但是,使用它会是这样的:
ExceptionHelper.CheckAndThrowArgNullEx(new [] {
new KeyValuePair<string, object>("someParam", someParam),
new KeyValuePair<string, object>("someOtherParam", someOtherParam),
... });
...这并没有真正帮助简化代码。 Tuple.Create()而不是KVP不起作用,因为Tuple的GTP不协变(即使IEnumerable的GTP是)。有任何想法吗?
C#7的更新
您可以将throw expression与null合并运算符一起使用。以下是该页面的示例:
public string Name
{
get => name;
set => name = value ??
throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}
原始答案
就个人而言,我使用ThrowIfNull
扩展方法。我不知道该归功于谁,但我绝对是didn't invent it。这很好,因为你可以使用返回值进行赋值:
public static T ThrowIfNull<T>(this T argument, string argumentName)
{
if (argument == null)
{
throw new ArgumentNullException(argumentName);
}
return argument;
}
用法:
this.something = theArgument.ThrowIfNull("theArgument");
// or in C# 6
this.something = theArgument.ThrowIfNull(nameof(theArgument));
(虽然有些人认为在null实例上调用扩展方法很奇怪)
如果你真的想一次检查多个参数,如果使用params
签名,你的例子可能会更精简:
public static void CheckAndThrowArgNullEx(params object[] argsAndNames)
{
for (int i = 0; i < argsAndNames.Length; i += 2)
{
if (argsAndNames[i] == null)
{
string argName = (string)argsAndNames[i + 1];
throw new ArgumentNullException(argName);
}
}
}
用法是:
CheckAndThrowArgNullEx(arg1, "arg1", arg2, "arg2");
// or in C# 6
CheckAndThrowArgNullEx(arg1, nameof(arg1), arg2, nameof(arg2));
第二个想法,正如KeithS在评论中提到的那样,将它实现为一组重载可能会更好,而不是像这样使用params object[]
:
static void Check(object arg1, string arg1Name) { ... }
static void Check(object arg1, string arg1Name, object arg2, string arg2Name) { ... }
// and so on...
我写了基准应用程序,提取参数名称的多种变体(通过匿名类+反射/ MemberExpression / Func /等)
Github链接到基准资源:https://github.com/iXab3r/NullCheckCompetition
我得到的结果显示,最快的方法是使用匿名类。
.NET 40 / X64
失败(即参数IS为null并执行名称提取方法)
- Falconimosclaus 67.87盎司
- FailDoubleLambda 643.98 ns
- FailLazyAnonymousClass 69.36 ns
- FailRawCheck 1.08 ns
- FailSingleLambda 643.27 ns
成功(即参数不为空)
- SuccessAnonymousClass 6.33 ns
- SuccessDoubleLambda 8.48 ns
- SuccessLazyAnonymousClass 8.78 ns
- SuccessRawCheck 1.08 ns
- SuccessSingleLambda 628.28 ns
实际上,可以从lambda表达式中检索参数名称,而无需通过Expression类型。这是如何做到的:
static void SampleMethod(string arg1)
{
ThrowIfNull(() => arg1);
// continue to other normal stuff here...
}
public static void ThrowIfNull<T>(Func<T> lambda)
where T : class
{
if (lambda() == null)
{
throw new ArgumentNullException(lambda.Target.GetType().GetFields()[0].Name);
}
}
我认为上面的大部分都是可以的,但是它们都没有真正改善你已经拥有的东西,所以我会选择KIS,Keep It Simple,这就是你的开始。
它干净,极其可读且速度快。它唯一有点长
有几种方法可以解决这个问题。
选项A:
将你的功能分解为两个 - 验证和实现(你可以在Jon Skeet的EduLinq中看到这个例子)。
选项B:
使用期望参数为非null的code contracts。
选项C:
使用面向方面的技术(如代码编织)将这些检查提取到一个方面。 (作为J Torres answered)。
选项D:
使用Spec#,作为CodeInChaos commented。
选项E:
???
你们大多数人的Upticks;你的答案有助于我最终得到的解决方案,其中包含了点点滴滴但最终与所有这些解决方案不同。
我创建了一些静态方法,它们处理特定形式的lambda表达式(EDIT - 小变化;方法不能是通用的,或者它们需要所有表达式返回相同的类型.Func很好,而且有一个额外的条件在GetName方法中展开强制转换):
public static class ExpressionReader
{
/// <summary>
/// Gets the name of the variable or member specified in the lambda.
/// </summary>
/// <param name="expr">The lambda expression to analyze.
/// The lambda MUST be of the form ()=>variableName.</param>
/// <returns></returns>
public static string GetName(this Expression<Func<object>> expr)
{
if (expr.Body.NodeType == ExpressionType.MemberAccess)
return ((MemberExpression) expr.Body).Member.Name;
//most value type lambdas will need this because creating the
//Expression from the lambda adds a conversion step.
if (expr.Body.NodeType == ExpressionType.Convert
&& ((UnaryExpression)expr.Body).Operand.NodeType
== ExpressionType.MemberAccess)
return ((MemberExpression)((UnaryExpression)expr.Body).Operand)
.Member.Name;
throw new ArgumentException(
"Argument 'expr' must be of the form ()=>variableName.");
}
}
public static class ExHelper
{
/// <summary>
/// Throws an ArgumentNullException if the value of any passed expression is null.
/// </summary>
/// <param name="expr">The lambda expressions to analyze.
/// The lambdas MUST be of the form ()=>variableName.</param>
/// <returns></returns>
public static void CheckForNullArg(params Expression<Func<object>>[] exprs)
{
foreach (var expr in exprs)
if(expr.Compile()() == null)
throw new ArgumentNullException(expr.GetName());
}
}
......因此可以使用:
//usage:
ExHelper.CheckForNullArg(()=>someParam, ()=>someOtherParam);
这样可以在没有第三方工具的情况下将样板减少到一行。 ExpressionReader以及异常生成方法可以处理在调用者中编译的form()=> variableName的任何lambda,这意味着它至少适用于局部变量,参数,实例字段和实例属性。我没有检查它是否适用于静力学。
public class TestClass
{
public TestClass()
{
this.ThrowIfNull(t=>t.Str, t=>t.Test);
//OR
//this.ThrowIfNull(t => t.X)
// .ThrowIfNull(t => t.Test);
}
string Str = "";
public TestClass Test {set;get;}
}
public static class SOExtension
{
public static T ThrowIfNull<T>(this T target, params Expression<Func<T, object>>[] exprs)
{
foreach (var e in exprs)
{
var exp = e.Body as MemberExpression;
if (exp == null)
{
throw new ArgumentException("Argument 'expr' must be of the form x=>x.variableName");
}
var name = exp.Member.Name;
if (e.Compile()(target) == null)
throw new ArgumentNullException(name,"Parameter [" + name + "] can not be null");
}
return target;
}
}
试试这个:一行。
accounts = accounts ?? throw new ArgumentNullException(nameof(accounts));
另外,使用nameof()
,如果变量被重命名,你将不必追捕所有“变量”,让nameof()
这样做。
如果您不反对第三方实用程序,PostSharp提供了注入此类验证的简洁方法。 This blog post为您的问题提供解决方案。
更新:请参阅新的Validating-parameters features in PostSharp 3
在构造函数中抛出 ArgumentNullException?
获得未处理的异常:System.ArgumentNullException
.Net Core Migration System.ArgumentNullException:值不能为空
[ArgumentNullException: 值不能为 null。
从 Web 窗体支架访问 EF 项目 - System.ArgumentNullException
应用程序引发了未处理的异常。 System.ArgumentNullException:值不能为空。 (参数'uriString')