重构安全ArgumentException的Lambda表达式

时间:2012-05-25 18:28:13

标签: c# .net .net-4.0 lambda

  

更新:这已不再是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的环境?

1 个答案:

答案 0 :(得分:0)

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

我不确定你当前的实现是否也适用于结构(虽然我可能会记错...我正在考虑处理拳击的情况可能只适用于Expression<Func<T, object>>(请阅读亲自试试吧。

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

至于表现:请在这里非常小心。你已经说过传递两个参数会更高效,所以你不需要编译和评估lambda ....但是要清楚,你每次编译时都会谈到15到30ms的命中率。评价。