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

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

注意 :有关此问题的一般化以及一些答案,请参阅“ 通过lambda表达式在运行时获取局部变量(和参数)的名称 ”。

我喜欢使用lambda表达式创建INotifyPropertyChanged接口的重构安全实现的想法,使用类似于Eric De Carufel提供的代码。

我正在尝试实现类似的东西,以便以重构安全的方式为ArgumentException (或其派生类)提供参数名称。

我已经定义了以下实用方法来执行null检查:

 public static void CheckNotNull(Expression<Func> parameterAccessExpression) { Func parameterAccess = parameterAccessExpression.Compile(); T parameterValue = parameterAccess(); CheckNotNull(parameterValue, parameterAccessExpression); } public static void CheckNotNull(T parameterValue, Expression<Func> parameterAccessExpression) { if (parameterValue == null) { Expression bodyExpression = parameterAccessExpression.Body; MemberExpression memberExpression = bodyExpression as MemberExpression; string parameterName = memberExpression.Member.Name; throw new ArgumentNullException(parameterName); } } 

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

 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> (阅读,请自己尝试)。

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

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