4

我想验证多个参数并抛出一个ArgumentNullException如果它们中的任何一个是null. 为了争论,让我们假设我有这个:

public void DoSomething(SomeClass param1, SomeClass param2, SomeClass param3);

当然,我可以这样做:

if (param1 == null)
    throw new ArgumentNullException(nameof(param1));
if (param2 == null)
    throw new ArgumentNullException(nameof(param2));
if (param3 == null)
    throw new ArgumentNullException(nameof(param3));

但这并不是特别漂亮,特别是如果它是整个应用程序中的重复签入。所以,我想我会这样做:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject)
    {
        if (T == null)
            throw new ArgumentNullException();
    }
}

// ...

param1.NullCheck();
param2.NullCheck();
param3.NullCheck();

但是这样我就输了nameof。我不能这样做nameof(subject),因为那毫无意义。

当然,这是一个选项:

public static class ValidationExtensions
{
    public static void NullCheck<T>(this T subject, string parameterName)
    {
        if (T == null)
            throw new ArgumentNullException(parameterName);
    }
}

// ...

param1.NullCheck(nameof(param1));
param2.NullCheck(nameof(param2));
param3.NullCheck(nameof(param3));

但它似乎容易出错,重复的参数......而且,老实说,只是不漂亮。

有这样做的好方法吗?理想情况下不使用任何外部库。

4

3 回答 3

3

最简洁和可维护的解决方案就是你所拥有的,或者 C#7 Throw Expression

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

你可以使用Expressions和一些聪明的东西,虽然我不推荐这个,它的气味和隐藏在抽象开销后面的简单逻辑。此外,它依赖于未来可能改变的未指定行为

然而,除此之外,我给你表情

public static class Validator
{
   public static void Validate<T>(Expression<Func<string, T>> f)
   {
      var name = (f.Body as MemberExpression).Member.Name;
      if(f.Compile().Invoke(name) == null)
         throw new ArgumentNullException(name);    
   }
}

之所以可行,是因为编译器为 lambda 表达式(闭包)生成了一个类,并且局部变量变成了 property Member.Name,这意味着它也应该适用于属性(未经测试)

用法

public static void Test(string param1, string param2)
{
   Validator.Validate(x => param1);
}

public static void Main()
{
   Test(null,"asdf");
}

输出

值不能为空。参数名称:param1

注意:说实话,我并没有考虑太多,也没有对它进行过几个用例的测试,它可能有效,也可能无效,所以我不对你用这段代码伤害的人负责

于 2019-03-06T00:24:13.657 回答
0

当心那些“代码优化”。反射树和表达式树会带来性能损失。

甚至您的泛型选项也会导致开发人员不必要地使用它。至少,为其添加一个约束:

public static void NullCheck<T>(this T subject, string parameterName) where T : class

当然,这也不是免费的。

随着在C# 7.0中throw作为表达式的引入(正如Michael Randall在他的回答中所展示的那样),这是一个单一的衬里,Visual Studio 将为您做到这一点。

于 2019-03-06T13:08:50.470 回答
0

.NET 6 (C# 10)您可以使用ArgumentNullException.ThrowIfNull

ArgumentNullException.ThrowIfNull(param1, nameof(param1));
ArgumentNullException.ThrowIfNull(param2, nameof(param2));
ArgumentNullException.ThrowIfNull(param3, nameof(param3));

目前还没有多个参数的选项,但您可以添加以下帮助程序:

public static class ArgumentNullExceptionHelpers
{
    public static void ThrowIfNull(params object[] arguments)
    {
        ArgumentNullException.ThrowIfNull(arguments);

        foreach (object argument in arguments)
        {
            ArgumentNullException.ThrowIfNull(argument);
        }
    }

    public static void ThrowIfNull(
        params (object? Argument, string ParamName)[] value)
    {
        foreach ((object? argument, string paramName) in value)
        {
            ArgumentNullException.ThrowIfNull(argument, paramName);
        }
    }
}

然后调用它:

ArgumentNullExceptionHelpers.ThrowIfNull(param1, param2, param3);

// Or - with argument names
ArgumentNullExceptionHelpers.ThrowIfNull(
    (param1, nameof(param1)), (param2, nameof(param2)), (param3, nameof(param3)));
于 2021-12-20T17:06:33.390 回答