重构安全ArgumentException的Lambda表达式

Dou*_*las 6 .net c# lambda .net-4.0

更新:这不再是来自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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以使用以下语法以重构安全的方式执行参数验证:

CheckNotNull(() => arg);           // most concise
CheckNotNull(arg, () => args);     // equivalent, but more efficient
Run Code Online (Sandbox Code Playgroud)

我关注的是以下几点:

Expression bodyExpression = parameterAccessExpression.Body;
MemberExpression memberExpression = bodyExpression as MemberExpression;
Run Code Online (Sandbox Code Playgroud)

A MemberExpression代表"访问字段或属性".保证在这种INotifyPropertyChanged情况下正常工作,因为lambda表达式将是属性访问.

但是,在我上面的代码中,lambda表达式在语义上是一个参数访问,而不是字段或属性访问.代码工作的唯一原因是C#编译器将在匿名函数中捕获的任何局部变量(和参数)提升为幕后编译器生成的类中的实例变量.这一点得到了Jon Skeet的证实.

我的问题是:这种行为(将捕获的参数提升为实例变量)是否记录在.NET规范中,或者它只是一个可能在框架的替代实现或未来版本中发生变化的实现细节?具体来说,是否存在parameterAccessExpression.Body is MemberExpression回报的环境false

Jef*_*eff 0

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

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

无论如何...所有这些都说了...它会在 C# 的未来版本中改变吗?可能不会。如果确实如此,您可以更改内部实现来处理它可能......

至于性能:请务必小心。您已经说过传递两个参数会更有效,因此您不需要编译和评估 lambda...但要清楚的是,您每次编译时都会在此处讨论 15 到 30 毫秒,并且评价。