ArgumentNullException - 如何简化?

时间:2012-08-20 19:28:52

标签: c# exception

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

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是)。有什么想法吗?

13 个答案:

答案 0 :(得分:16)

C#7更新

您可以将throw expression与空合并运算符一起使用。以下是该页面的示例:

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

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

如果您确实想一次检查多个参数,如果您使用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...

答案 1 :(得分:7)

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

选项A:

将你的功能分解为两个 - 验证和实现(你可以在Jon Skeet的EduLinq中看到这个例子。)

选项B:

使用期望参数非空的code contracts

选项C:

使用面向方面的技术(如代码编织)将这些检查提取到一个方面。 (作为J Torres answered)。

选项D:

使用Spec#,作为CodeInChaos commented

选项E:

???

答案 2 :(得分:4)

对你们大多数人来说都是不妥之处;你的答案有助于我最终得到的解决方案,其中包含了点点滴滴,但最终与所有这些解决方案不同。

我创建了一些静态方法,它们处理特定形式的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以及异常生成方法可以处理在调用者中编译的form()=&gt; variableName的任何lambda,这意味着它至少适用于局部变量,参数,实例字段和实例属性。我没有检查它是否适用于静力学。

答案 3 :(得分: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;
    }
}

答案 4 :(得分:3)

试试这个:一行。

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

另外,使用nameof(),如果变量被重命名,则不必追捕所有“变量”,让nameof()这样做。

答案 5 :(得分:2)

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

更新:查看新的Validating-parameters features in PostSharp 3

答案 6 :(得分:1)

扩展方法怎么样?

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

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

someParam.ThrowExceptionIfNull("someParam");

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

答案 7 :(得分: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
}

缺点是它有点不可扩展,并且不容易检查是否只有一些参数为空。

答案 8 :(得分:0)

嗯,样板很难避免。您可以切换到使用Bertrand Meyers的Eiffel programming languageEiffelStudio而不是C#和Visual Studio,然后开始练习“design by contract”。

Eiffel现在完全符合CLR标准。

答案 9 :(得分:0)

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

Github链接到基准来源:https://github.com/iXab3r/NullCheckCompetition

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

.NET 40 / X64

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

  • 失败 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

答案 10 :(得分:0)

实际上可以从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);
    }
}

答案 11 :(得分:0)

我认为上面的大部分内容都可以,但是它们都不是对你已经拥有的东西的改进,所以我会选择KIS,Keep It Simple,这就是你的开始。

它干净,极其可读且速度快。唯一有点长的

答案 12 :(得分:0)

在c#7中可以这样完成:

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

优化发行版后,您将获得:

if (someParam == null)
    throw new ArgumentNullException(nameof(someParam));