检查空参数的最佳方法(Guard子句)

时间:2015-03-21 16:10:10

标签: c# constructor arguments

例如,您通常不希望构造函数中的参数为null,因此看到某些内容非常正常

if (someArg == null)
{
    throw new ArgumentNullException(nameof(someArg));
}

if (otherArg == null)
{
    throw new ArgumentNullException(nameof(otherArg));
}

它会使代码混乱。

有没有办法比这更好地检查一个参数列表的参数?

类似于"检查所有参数并抛出ArgumentNullException,如果它们中的任何一个为null并且为您提供了null的参数。

顺便说一下,关于重复的问题声明,这不是关于用属性或内置的东西标记参数,而是有人称之为Guard Clauses以保证对象接收初始化的依赖关系。

11 个答案:

答案 0 :(得分:15)

public static class Ensure
{
    /// <summary>
    /// Ensures that the specified argument is not null.
    /// </summary>
    /// <param name="argumentName">Name of the argument.</param>
    /// <param name="argument">The argument.</param>
    [DebuggerStepThrough]
    [ContractAnnotation("halt <= argument:null")]        
    public static void ArgumentNotNull(object argument, [InvokerParameterName] string argumentName)
    {
        if (argument == null)
        {
            throw new ArgumentNullException(argumentName);
        }
    }
}

用法:

// C# < 6
public Constructor([NotNull] object foo)
{
    Ensure.ArgumentNotNull(foo, "foo");
    ...
}

// C# >= 6
public Constructor([NotNull] object bar)
{
    Ensure.ArgumentNotNull(bar, nameof(bar));
    ...
}

DebuggerStepThroughAttribute非常方便,以便在调试时(或在发生异常后附加调试器时)进行激活时,我不会在ArgumentNotNull方法内部结束,而是在空引用actually发生的调用方法。

我正在使用ReSharper Contract Annotations

  • ContractAnnotationAttribute确保我永远不会拼错 参数("foo")并在重命名时自动重命名 foo符号。
  • NotNullAttribute帮助ReSharper进行代码分析。因此,如果我{Revharper}发出警告,那么这将导致异常。new Constructor(null)
  • 如果您不想直接注释代码, 您也可以使用可以在库中部署的external XML-files执行相同的操作,并且用户可以在他们的ReShaprer中进行最佳引用。

答案 1 :(得分:12)

如果你的构造函数中有太多参数,你可以更好地修改它们,但这是另一个故事。

为了减少样板验证代码,许多人编写了这样的Guard实用程序类:

public static class Guard
{
    public static void ThrowIfNull(object argumentValue, string argumentName)
    {
        if (argumentValue == null)
        {
            throw new ArgumentNullException(argumentName);
        }
    }

    // other validation methods
}

(您可以添加该Guard类可能需要的其他验证方法。)

因此,只需要一行代码来验证参数:

    private static void Foo(object obj)
    {
        Guard.ThrowIfNull(obj, "obj");
    }

答案 2 :(得分:6)

空引用是你必须要防范的一种麻烦。但是,他们不是唯一的一个。问题比这更广泛,可归结为:方法接受某种类型的实例,但它无法处理所有实例。

换句话说,该方法的域大于它处理的值集。然后使用Guard子句断言实际参数不属于方法域的“灰色区域”,无法处理。

现在,我们有空引用,作为一个通常在可接受的值集之外的值。另一方面,经常发生集合中的一些非null元素也是不可接受的(例如空字符串)。

在这种情况下,可能会发现方法签名过于宽泛,从而表明存在设计问题。这可能导致重新设计,例如定义子类型,通常是派生接口,它限制方法的域并使一些保护子句消失。您可以在本文中找到一个示例:Why do We Need Guard Clauses?

答案 3 :(得分:3)

使用新版本的C#语言,您可以编写此代码而无需其他库或其他方法调用:

_ = someArg ?? throw new ArgumentNullException(nameof(someArg));
_ = otherArg ?? throw new ArgumentNullException(nameof(otherArg));

答案 4 :(得分:2)

在C#8.0和更高版本中,可以使用新的帮助。 C#8.0引入了不可为空的引用类型(此功能在文档中有些令人困惑地称为“可为空的引用类型”)。在C#8.0之前,所有引用类型都可以设置为null。但是现在有了C#8.0和'nullable'项目设置,我们可以说默认情况下引用类型是非空的,然后根据情况使变量和参数为空。

因此,目前,我们可以识别如下代码:

public void MyFunction(int thisCannotBeNull, int? thisCouldBeNull)
{
    // no need for checking my thisCannotBeNull parameter for null here
}

如果您为C#v8.0 +项目设置了<Nullable>enable</Nullable>,您也可以执行以下操作:

public void MyFunction(MyClass thisCannotBeNull, MyClass? thisCouldBeNull)
{
    // static code analysis at compile time checks to see if thisCannotBeNull could be null
}

使用静态代码分析在编译时进行空检查。因此,如果您以某种方式进行编码(这意味着可能会在其中出现null),则会收到编译器警告(如果需要,可以将其升级为错误)。因此,很多(但不是全部)需要对空参数进行运行时检查的情况可以作为基于代码的编译时检查来处理。

答案 5 :(得分:1)

有一个名为SwissKnife的nuget包。从nuget gallery安装SwissKnife。它为您提供了许多选项,从null检查参数开始 Argument.IsNotNullOrEmpty(args,"args")命名空间下的SwissKnife.Diagnostics.Contracts以及选项idoim等等。您可以设置Option<Class_Name> _someVar,然后检查_someVar.IsSome_someVar.IsNone。这也有助于反对可空类。希望这会有所帮助。

答案 6 :(得分:1)

您可以尝试使用我的Heleonix.Guard库,该库提供了防护功能。

您可以编写如下保护条款:

// C# 7.2+: Non-Trailing named arguments
Throw.ArgumentNullException(when: param.IsNull(), nameof(param));

// OR

// Prior to C# 7.2: You can use a helper method 'When'
Throw.ArgumentNullException(When(param.IsNull()), nameof(param));

// OR
Throw.ArgumentNullException(param == null, nameof(param));

// OR
Throw.ArgumentNullException(When (param == null), nameof(param));

它提供了许多现有异常的抛出,并且您可以为定制异常编写定制扩展方法。同样,该库引用了带有谓词扩展名(例如IsNullIsNullOrEmptyOrWhitespaceIsLessThan等)的“ Heleonix.Extensions”库,以根据所需值检查参数或变量。与其他一些具有流畅接口的防护库不同,这些扩展不会生成中间对象,并且由于实现起来非常简单,因此性能很高。

答案 7 :(得分:1)

Ardalis具有出色的GuardClauses库。

使用Guard.Against.Null(message, nameof(message));

很高兴

答案 8 :(得分:1)

我发现的最简单的方法是Dapper使用匿名类型的启发。 我编写了一个使用匿名类型获取属性名称的Guard类。 警卫本身如下

    public class Guard
    {
        public static void ThrowIfNull(object param) 
        {
            var props = param.GetType().GetProperties();
            foreach (var prop in props) 
            {
                var name = prop.Name;
                var val = prop.GetValue(param, null);

                _ = val ?? throw new ArgumentNullException(name);
            }
        }
    }

那么用途就是

...
        public void Method(string someValue, string otherValue)
        {
            Guard.ThrowIfNull(new { someValue, otherValue }); 
        }
...

当引发ArgumentNullException并显示正确命名的属性时。

答案 9 :(得分:1)

C# 10 介绍了执行此操作的最短方法(到目前为止)。在要检查是否为空的参数之后,您只需要 2 个感叹号 !!

之前:

public void test(string someArg){
    if (someArg == null)
    {
        throw new ArgumentNullException(nameof(someArg));
    }
    // other code
}

使用 C# 10 或更高版本:

public void test(string someArg!!){
    // other code
}

如果你调用 test(null),将会抛出一个 ArgumentNullException,告诉你 someArg 是 null

来源:A video on the 'Microsoft Developer' YouTube channel explaining the new feature


请注意:这是 C# 10 的一项功能,C# 10 尚未正式发布。它将于 2021 年 11 月 (according to Microsoft) 随 .Net6 一起发布。

答案 10 :(得分:0)

如果您要保存两次输入参数名称,例如 Guard.AgainstNull(arg,nameof(arg));

签出YAGuard,您可以在其中写 Guard.AgainstNull(arg);

无需在guard子句中指定参数的名称,但是在引发的参数中,名称可以正确解析。

它还支持以下形式的保护和设置 MyProperty = Assign.IfNotNull(arg);

Nuget:YAGuard

免责声明:我是YAGuard的作者。