ArgumentNullException - 如何简化?

Kei*_*thS 16 c# exception

我注意到这个代码在我的构造函数中出现了很多:

if (someParam == null) throw new ArgumentNullException("someParam");
if (someOtherParam == null) throw new ArgumentNullException("someOtherParam");
...
Run Code Online (Sandbox Code Playgroud)

我有一些构造函数,其中注入了几个东西,并且必须都是非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);
   }
}
Run Code Online (Sandbox Code Playgroud)

但是,使用它会是这样的:

ExceptionHelper.CheckAndThrowArgNullEx(new [] {
    new KeyValuePair<string, object>("someParam", someParam),
    new KeyValuePair<string, object>("someOtherParam", someOtherParam),
    ... });
Run Code Online (Sandbox Code Playgroud)

...这并没有真正帮助简化代码.Tuple.Create()而不是KVP不起作用,因为Tuple的GTP不协变(即使IEnumerable的GTP是).有任何想法吗?

def*_*mer 18

C#7的更新

您可以将throw表达式与null合并运算符一起使用.以下是该页面的示例:

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");
}
Run Code Online (Sandbox Code Playgroud)

原始答案

就个人而言,我使用ThrowIfNull扩展方法.我不知道该归功于谁,但我绝对没有发明它.这很好,因为你可以使用返回值进行赋值:

public static T ThrowIfNull<T>(this T argument, string argumentName)
{
    if (argument == null)
    {
        throw new ArgumentNullException(argumentName);
    }
    return argument;
}
Run Code Online (Sandbox Code Playgroud)

用法:

this.something = theArgument.ThrowIfNull("theArgument");
// or in C# 6
this.something = theArgument.ThrowIfNull(nameof(theArgument));
Run Code Online (Sandbox Code Playgroud)

(虽然有些人认为在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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法是:

CheckAndThrowArgNullEx(arg1, "arg1", arg2, "arg2");
// or in C# 6
CheckAndThrowArgNullEx(arg1, nameof(arg1), arg2, nameof(arg2));
Run Code Online (Sandbox Code Playgroud)

第二个想法,正如KeithS在评论中提到的那样,将它实现为一组重载可能会更好,而不是params object[]像这样使用:

static void Check(object arg1, string arg1Name) { ... }
static void Check(object arg1, string arg1Name, object arg2, string arg2Name) { ... }
// and so on...
Run Code Online (Sandbox Code Playgroud)


KUT*_*ime 15

.NET 6 及更高版本

.NET API 中 有一个新方法ArgumentNullException.ThrowIfNull(someParameter)

这种方法可能是您可以获得的最佳选择。

C# 11(拒绝的提案

在参数上使用新的 Bang Bang 运算符!!来隐式检查null.

public string SomeFunction(Foo foo!!)
{
  // here, foo is already checked for null
  // ArgumentNullException(nameof(foo)) is thrown when foo = null
  return $"Here is {foo.Bar}";
}
Run Code Online (Sandbox Code Playgroud)

长话短说

编译器将为每次!!使用发出此代码

if (someArgument is null)
{
  throw new ArgumentNullException(nameof(someArgument));
}
Run Code Online (Sandbox Code Playgroud)

我们的SomeFunction意志将转变为

public string SomeFunction(Foo foo!!)
{
  if (foo is null)
  {
    throw new ArgumentNullException(nameof(foo));
  }
  return $"Here is {foo.Bar}";
}
Run Code Online (Sandbox Code Playgroud)


Rus*_*ing 9

试试这个:一行。

accounts = accounts ?? throw new ArgumentNullException(nameof(accounts));
Run Code Online (Sandbox Code Playgroud)

此外,使用nameof(),如果变量被重命名,您将不必寻找所有“变量”,让我们nameof()这样做。

  • 或者你可以做`_=账户?抛出新的ArgumentNullException(名称(帐户));` (7认同)

Yga*_*bel 8

在 c# 10 中你可以这样做:

ArgumentNullException.ThrowIfNull(z);
Run Code Online (Sandbox Code Playgroud)

你会得到这个错误:

System.ArgumentNullException: Value cannot be null. (Parameter 'z')
   at System.ArgumentNullException.Throw(String paramName)
   at System.ArgumentNullException.ThrowIfNull(Object argument, String paramName)
   at ConsoleApp1.SomeClass.Join(String a, String b)
Run Code Online (Sandbox Code Playgroud)

在底层,它使用新的CallerArgumentExpression属性。


Ode*_*ded 7

有几种方法可以解决这个问题.

选项A:

将您的功能分解为两个 - 验证和实现(您可以在Jon Skeet的EduLinq中看到此示例).

选项B:

使用期望参数为非null的代码协定.

选项C:

使用面向方面的技术(如代码编织)将这些检查提取到一个方面.(正如J托雷斯回答的那样).

选项D:

使用Spec#,如CodeInChaos所评论.

选项E:

???


Kei*_*thS 5

你们大多数人的上涨;您的回答促成了我最终得出的解决方案,其中包含了点点滴滴,但最终与所有解决方案都不同。

我创建了几个处理特定形式的 lambda 表达式的静态方法(编辑- 小改动;这些方法不能是通用的,否则它们将要求所有表达式都返回相同的类型。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());
    }
}
Run Code Online (Sandbox Code Playgroud)

...可以这样使用:

//usage:

ExHelper.CheckForNullArg(()=>someParam, ()=>someOtherParam);
Run Code Online (Sandbox Code Playgroud)

这将样板文件减少到一行,无需第三方工具。ExpressionReader 以及异常生成方法适用于在调用者中编译的任何形式为 ()=>variableName 的 lambda,这意味着它至少适用于局部变量、参数、实例字段和实例属性。我还没有检查它是否适用于静态。

  • 这不是[携带(相对)沉重的运行时成本](http://codereview.stackexchange.com/a/45196/18856)吗? (3认同)