重构安全参数异常的 Lambda 表达式
本文关键字:Lambda 表达式 异常 安全 参数 重构 | 更新日期: 2023-09-27 18:35:31
更新:这不再是 C# 6 中的问题,C# 6 引入了
nameof
运算符来解决此类情况(请参阅 MSDN)。注意:请参阅"在运行时通过 lambda 表达式获取局部变量(和参数)的名称",了解此问题的概括以及一些答案。
我喜欢使用 lambda 表达式创建 INotifyPropertyChanged
接口的重构安全实现的想法,使用类似于 Eric De Carufel 提供的代码。
我正在尝试实现类似的东西,以重构安全的方式向ArgumentException
(或其派生类)提供参数名称。
我定义了以下实用程序方法来执行null
检查:
public static void CheckNotNull<T>(Expression<Func<T>> parameterAccessExpression)
{
Func<T> parameterAccess = parameterAccessExpression.Compile();
T parameterValue = parameterAccess();
CheckNotNull(parameterValue, parameterAccessExpression);
}
public static void CheckNotNull<T>(T parameterValue,
Expression<Func<T>> parameterAccessExpression)
{
if (parameterValue == null)
{
Expression bodyExpression = parameterAccessExpression.Body;
MemberExpression memberExpression = bodyExpression as MemberExpression;
string parameterName = memberExpression.Member.Name;
throw new ArgumentNullException(parameterName);
}
}
然后,可以使用以下语法以重构安全的方式执行参数验证:
CheckNotNull(() => arg); // most concise
CheckNotNull(arg, () => args); // equivalent, but more efficient
我关心的是以下几行:
Expression bodyExpression = parameterAccessExpression.Body;
MemberExpression memberExpression = bodyExpression as MemberExpression;
MemberExpression
表示"访问字段或属性"。保证在INotifyPropertyChanged
情况下正常工作,因为 lambda 表达式将是属性访问。
但是,在上面的代码中,lambda 表达式在语义上是参数访问,而不是字段或属性访问。代码工作的唯一原因是 C# 编译器将匿名函数中捕获的任何局部变量(和参数)提升为后台编译器生成的类中的实例变量。乔恩·斯基特(Jon Skeet)证实了这一点。
我的问题是:这种行为(将捕获的参数提升为实例变量)是否记录在 .NET 规范中,或者它只是一个可能在框架的替代实现或未来版本中更改的实现细节?具体来说,可能存在parameterAccessExpression.Body is MemberExpression
返回false
的环境?
闭包:如前所述,对于参数访问,C# 编译器(是的,特别是编译器)创建一个闭包类,其中包含用于存储捕获的参数变量值的实例字段。这是否会随着 C# 编译器的未来版本而改变?确定。也许在 C# 的未来版本中,生成的闭包类将具有随机命名的变量,因为名称在运行时并不重要。此外,您拥有的代码可能不适用于其他 .NET 语言。您会注意到 VB .NET 生成的表达式树和闭包类有时与 C# 略有不同......
我不确定您当前的实现是否也适用于结构(尽管我可能记错了......我想处理拳击的情况可能只适用于Expression<Func<T, object>>
(阅读,请自己尝试)。
无论如何。。。这些都说...它会在未来版本的 C# 中发生变化吗?应该不会。如果是这样,您可以更改内部实现来处理它......
至于性能:请在这里非常小心。您已经说过传递两个参数会更有效,因此您无需编译和评估 lambda....但需要明确的是,您在这里谈论的是每次编译和评估时 15 到 30 毫秒的命中。