对多个参数进行空检查并使用它们的名称抛出异常
Null-check multiple parameters and throw an exception with their name
我想验证多个参数并抛出一个 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));
但它似乎容易出错,重复的参数...而且,老实说,就是不漂亮。
有什么好的方法吗?理想情况下不使用任何外部库。
最简洁和可维护的解决方案就是您所拥有的,或者 C#7 Throw Expression
param1 = param1 ?? throw new ArgumentNullException(nameof(param1));
你可以使用表达式和一些技巧,虽然我不推荐这个,它的味道 并隐藏了 抽象 和 开销 背后的简单逻辑。此外,它依赖于 未指定的行为,将来可能会改变
然而,抛开这些,我给你表达式
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 表达式(一个闭包)生成了一个 class,并且局部变量变成了一个 属性 Member.Name
,这意味着它也应该适用于属性(未经测试)
用法
public static void Test(string param1, string param2)
{
Validator.Validate(x => param1);
}
public static void Main()
{
Test(null,"asdf");
}
输出
Value cannot be null. Parameter name: param1
注意:说实话,我没有考虑太多或测试过几个用例,它可能有效也可能无效,所以我不对你用这个代码伤害的人负责
小心那些 "code optimizations"。反射树和表达式树会带来性能损失。
即使是您的泛型选项也会导致开发人员不必要地使用它。至少,给它加个约束:
public static void NullCheck<T>(this T subject, string parameterName) where T : class
当然,这也不是免费的。
随着 throw
as an expression in C# 7.0 (as Michael Randall showed in his ) 的推出,它是一个班轮,Visual Studio 会为您做到这一点。
在 .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)));
我想验证多个参数并抛出一个 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));
但它似乎容易出错,重复的参数...而且,老实说,就是不漂亮。
有什么好的方法吗?理想情况下不使用任何外部库。
最简洁和可维护的解决方案就是您所拥有的,或者 C#7 Throw Expression
param1 = param1 ?? throw new ArgumentNullException(nameof(param1));
你可以使用表达式和一些技巧,虽然我不推荐这个,它的味道 并隐藏了 抽象 和 开销 背后的简单逻辑。此外,它依赖于 未指定的行为,将来可能会改变
然而,抛开这些,我给你表达式
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 表达式(一个闭包)生成了一个 class,并且局部变量变成了一个 属性 Member.Name
,这意味着它也应该适用于属性(未经测试)
用法
public static void Test(string param1, string param2)
{
Validator.Validate(x => param1);
}
public static void Main()
{
Test(null,"asdf");
}
输出
Value cannot be null. Parameter name: param1
注意:说实话,我没有考虑太多或测试过几个用例,它可能有效也可能无效,所以我不对你用这个代码伤害的人负责
小心那些 "code optimizations"。反射树和表达式树会带来性能损失。
即使是您的泛型选项也会导致开发人员不必要地使用它。至少,给它加个约束:
public static void NullCheck<T>(this T subject, string parameterName) where T : class
当然,这也不是免费的。
随着 throw
as an expression in C# 7.0 (as Michael Randall showed in his
在 .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)));