用于重构安全 ArgumentException 的 Lambda 表达式

Posted

技术标签:

【中文标题】用于重构安全 ArgumentException 的 Lambda 表达式【英文标题】:Lambda expressions for refactor-safe ArgumentException 【发布时间】:2012-06-01 08:15:31 【问题描述】:

更新:这不再是 C# 6 的问题,它引入了 nameof 运算符来解决此类情况(请参阅 MSDN)。

注意:请参阅“Getting names of local variables (and parameters) at run-time through lambda expressions”了解此问题的概括性以及一些答案。

我喜欢使用 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的环境?

【问题讨论】:

CheckNotNull(() =&gt; ((object)5) as string); 呢? 就我个人而言,我认为Contract.Requires&lt;TException&gt; 所做的一切对我来说都足够了。如果先决条件失败,则为错误。无需担心细节。 @CodeInChaos:那些可供第三方使用的库呢?预计公共方法将执行参数检查并使用正确的参数名称抛出 ArgumentException @leppie:我的问题涉及 argument 验证。目标是获取参数名称,而不是评估任意表达式。 这里的替代方案:***.com/questions/869610/…,同样没有记录。但更快。 【参考方案1】:

闭包:正如您所说,对于参数访问,C# 编译器(是的,特别是编译器)创建一个包含实例字段的闭包类来存储您捕获的参数变量的值。这会随着 C# 编译器的未来版本而改变吗?当然。也许在未来的 C# 版本中,生成的闭包类将具有随机命名的变量,因为名称在运行时并不重要。此外,您拥有的代码可能不适用于其他 .NET 语言。您会注意到 VB .NET 生成的表达式树和闭包类有时与 C# 略有不同...

我不确定您当前的实现是否也适用于结构(尽管我可能记错了......我正在考虑处理拳击的情况可能只适用于Expression&lt;Func&lt;T, object&gt;&gt;(阅读,请尝试自己)。

无论如何...所有这一切...在未来的 C# 版本中会改变吗?可能不是。如果是这样,你可以改变你的内部实现来处理它……

至于性能:这里请务必小心。您已经说过传递两个参数会更有效,因此您不需要编译和评估 lambda ......但为了清楚起见,您每次编译时都会在这里点击 15 到 30 毫秒评估。

【讨论】:

我接受这个答案,因为它提出了一些有效的观点(包括提升变量被重命名的可能性,以及与其他 .NET 语言的不兼容)。作为参考,我还在另一个问题下记录了我对lack of standardization of this behaviour 的研究。

以上是关于用于重构安全 ArgumentException 的 Lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章

如何使这个线程安全

Kafka安全机制解析及重构(四) ACL权限控制

漏洞? System.ArgumentException: '无法找出路线:

System.ArgumentException:“键 'attachdbfilename' 的值无效。”

CreateSQLQuery(string) 抛出 System.ArgumentException

HttpClient 抛出 System.ArgumentException:'windows-1251' 不是受支持的编码名称