通过lambda表达式在运行时获取局部变量(和参数)的名称

Dou*_*las 19 .net c# lambda

我有兴趣以重构安全的方式在运行时检索局部变量(和参数)的名称.我有以下扩展方法:

public static string GetVariableName<T>(Expression<Func<T>> variableAccessExpression)
{
    var memberExpression = variableAccessExpression.Body as MemberExpression;
    return memberExpression.Member.Name;
}
Run Code Online (Sandbox Code Playgroud)

...返回通过lambda表达式捕获的变量的名称:

static void Main(string[] args)
{
    Console.WriteLine(GetVariableName(() => args));
    // Output: "args"

    int num = 0;
    Console.WriteLine(GetVariableName(() => num));
    // Output: "num"
}
Run Code Online (Sandbox Code Playgroud)

但是,这只能起作用,因为C#编译器将在匿名函数中捕获的任何局部变量(和参数)提升为幕后编译器生成的类中的同名实例变量(每个Jon Skeet).如果不是这种情况,则转换为Bodyto MemberExpression会失败,因为MemberExpression代表字段或属性访问.

这个变量是促销记录的行为,还是一个实现细节可能会在框架的其他版本中发生变化?

注意:这个问题是我前一个关于参数验证的概括.

Dou*_*las 13

更新:这不再是来自C#6的问题,它引入了nameof运营商来解决这些问题(参见MSDN).

似乎我的问题的答案是否定的 ; 该功能是非标准化的.这种情况似乎比我原先怀疑的还要糟糕; 不仅是捕获变量的推广是非标准化的,而且将匿名函数转换为表达式树表示的整个规范也是如此.

这意味着即使是直接的匿名函数(如下所示)也不能保证在框架的不同实现中产生一致的表达式树(直到转换标准化为止):

Expression<Func<int, int, int>> add = (int x, int y) => x + y;
Run Code Online (Sandbox Code Playgroud)

以下摘录摘自C#语言规范4.0(在所有情况下都强调了).

从"4.6表达式树类型":

Expression<D>当匿名函数转换为表达式树类型时,泛型类型的确切定义以及构造表达式树的精确规则都在本规范的范围之外,并在别处描述.

从"6.5.2评估匿名函数转换到表达式树类型":

将匿名函数转换为表达式树类型会生成表达式树(第4.6节).更确切地说,对匿名函数转换的评估导致构造表示匿名函数本身的结构的对象结构.表达式树的精确结构以及创建它的确切过程是实现定义的.

"6.5.3实现示例"中的第三个示例演示了捕获局部变量的匿名函数的转换,并确认了我的问题中提到的变量提升:

现在必须将局部变量的生命周期延长到至少匿名函数委托的生命周期.这可以通过将局部变量"提升"到编译器生成的类的字段中来实现.然后,局部变量(第7.15.5.2节)的实例化对应于创建编译器生成的类的实例,并且访问局部变量对应于访问编译器生成的类的实例中的字段.

本节末尾进一步证实了这一点:

在将匿名函数转换为表达式树时,也可以使用此处应用于捕获局部变量的相同技术:对编译器生成的对象的引用可以存储在表达式树中,对局部变量的访问可以表示为对这些对象的字段访问.这种方法的优点是它允许在代理和表达式树之间共享"提升的"局部变量.

但是,该部分的开头有一个免责声明:

此处描述的实现基于Microsoft C#编译器使用的相同原则,但它绝不是强制实现,也不是唯一可行的实现.它只是简单地提到了对表达式树的转换,因为它们的确切语义超出了本规范的范围.

PS Eric Lippert 在这篇评论证实表达树规格从未发货.在CodePlex上的DLR文档下存在Expression Trees v2 Spec,但其范围似乎并未涵盖C#中匿名函数到表达式树的转换.


Nic*_*ler 1

AFAIK,这是一个实现细节。

然而,我认为你可以打赌它实际上不会改变。

我刚刚在 VS2012 RC 中进行了测试,它按预期工作 - 所以你至少在几年内是安全的。