21

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

if (someParam == null) throw new ArgumentNullException("someParam");
if (someOtherParam == null) throw new ArgumentNullException("someOtherParam");
...

我有几个构造函数,其中注入了一些东西,并且都必须是非空的。任何人都可以想出一种方法来简化这个吗?我唯一能想到的是以下几点:

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 是协变的)。有任何想法吗?

4

16 回答 16

25

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");
}

原始答案

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

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));

(虽然有些人认为在空实例上调用扩展方法很奇怪)

如果您真的想一次检查多个参数,如果您使用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...
于 2012-08-20T19:56:34.510 回答
13

试试这个:一条线。

accounts = accounts ?? throw new ArgumentNullException(nameof(accounts));

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

于 2018-03-06T17:03:00.197 回答
8

.NET 6 及更高版本

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

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

于 2021-11-06T08:19:48.477 回答
7

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

选项 A:

将您的函数分成两部分 - 验证和实现(您可以在 Jon Skeet 的EduLinq中看到这方面的示例)。

选项 B:

使用期望参数为非空的代码协定。

选项 C:

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

选项 D:

使用Spec#,正如 CodeInChaos评论的那样。

选项 E:

???

于 2012-08-20T19:31:26.727 回答
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());
    }
}

...可以这样使用:

//usage:

ExHelper.CheckForNullArg(()=>someParam, ()=>someOtherParam);

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

于 2012-08-20T20:41:31.777 回答
3
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;
    }
}
于 2012-08-20T19:57:30.387 回答
3

在 c# 10 中,您可以这样做:

ArgumentNullException.ThrowIfNull(z);

你会得到这个错误:

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)

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

于 2021-11-28T11:48:43.720 回答
3

在 c# 7 中可以这样做:

_ = someParam ?? throw new ArgumentNullException(nameof(someParam));

发布优化后您将获得:

if (someParam == null)
    throw new ArgumentNullException(nameof(someParam));
于 2020-03-19T15:19:18.603 回答
2

如果您不反对第三方实用程序,PostSharp 提供了注入此类验证的简洁方法。 这篇博文为您的问题提供了解决方案。

更新:查看PostSharp 3 中的新验证参数功能

于 2012-08-20T19:31:27.640 回答
1

好吧,样板是很难避免的。您可以改用 Bertrand Meyers 的Eiffel 编程语言EiffelStudio,而不是 C# 和 Visual Studio,并开始练习“按合同设计 ”。

如今,Eiffel 完全符合 CLR 标准。

于 2012-08-20T19:42:03.507 回答
1

扩展方法怎么样?

public static void ThrowExceptionIfNull(this object argument, string argumentName)
{
    if(argument == null)
        throw new ArgumentNullException(argumentName);
} 

然后你的代码至少读起来更流利一点:

someParam.ThrowExceptionIfNull("someParam");

否则,我会同意其他人拆分功能或使用 AOP(即 PostSharp)

于 2012-08-20T19:34:45.373 回答
1

已经有很多有效的解决方案,但这是我的看法:

using System.Diagnostics;
using System.Reflection;

public SomeConstructor(int? par1, int? par2, string par3)
{
    CheckThrowNull(par1, par2, par3);
    //rest of constructor code...
}

///<param name="values"> Values must be given in order </param>
public static void CheckThrowNull(params object[] values)
{
    StackTrace stackTrace = new StackTrace();
    ParameterInfo[] parameters = stackTrace.GetFrame(1).GetMethod().GetParameters(); //get calling method's parameters (or constructor)
    if (parameters.Length != values.Length)
    {
        throw new ArgumentException("Incorrect number of values passed in");
    }
    for (int i = 0; i < parameters.Length; i++)
    {
        if (values[i] == null)
        {   
            //value was null, throw exception with corresponding parameter name
            throw new ArgumentNullException(parameters[i].Name);
        }
    }
}

总体思路是建立两个并行数组,一个是 ParameterInfo 类型,一个包含参数的值。后者必须传入,因为参数值不容易(而且我认为不可能)通过反射获得。为了给予应得的荣誉,我在这里找到了如何获取调用方法:http ://www.csharp-examples.net/reflection-calling-method-name/

就个人而言,除了调试之外,我不喜欢使用 System.Diagnosics,所以我会稍作修改,调用代码为:

CheckThrowNull(MethodBase.GetCurrentMethod(), par1, par2, par3);

和方法是

CheckThrowNull(MethodBase method, params object[] values)
{
    ParameterInfo[] parameters = method.GetParameters();
    //rest of code same
}

不利的一面是它有点不可扩展,并且不能轻易检查是否只有一些参数为空。

于 2012-08-20T20:33:48.223 回答
1

我编写了具有多种提取参数名称的基准应用程序(通过匿名类 + 反射/ MemberExpression / Func / 等)

基准测试源的 Github 链接:https ://github.com/iXab3r/NullCheckCompetition

我得到的结果表明,最快的方法是使用匿名类。

.NET 40 / X64

失败(即参数为空且执行名称提取方法)

  • 失败AnonymousClass 67.87 ns
  • 失败DoubleLambda 643.98 ns
  • 失败LazyAnonymousClass 69.36 ns
  • 未通过RawCheck 1.08 ns
  • 失败SingleLambda 643.27 ns

成功(即参数不为空)

  • SuccessAnonymousClass 6.33 ns
  • SuccessDoubleLambda 8.48 ns
  • SuccessLazyAnonymousClass 8.78 ns
  • SuccessRawCheck 1.08 ns
  • SuccessSingleLambda 628.28 ns
于 2015-07-06T18:52:20.187 回答
0

C# 10 (.NET >= 6) 的更新:

_person = person.ThrowIfNull();

public static T ThrowIfNull<T>(this T? argument, string? message = default, [CallerArgumentExpression("argument")] string? paramName = default)
{
    return argument ?? throw new ArgumentNullException(paramName, message);
}

参考: https ://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression

于 2021-11-15T15:04:32.027 回答
0

我认为以上大部分内容都可以,但它们都不是对你已有的东西的真正改进,所以我会选择 KIS,保持简单,这就是你开始的。

它干净、易读且速度快。唯一有点长

于 2017-02-09T08:28:47.573 回答
-1

实际上可以从 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);
    }
}
于 2016-07-26T10:43:06.057 回答