20

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

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

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

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

    int num = 0;
    Console.WriteLine(GetVariableName(() => num));
    // Output: "num"
}

但是,这仅起作用,因为 C# 编译器将匿名函数中捕获的任何局部变量(和参数)提升为在后台编译器生成的类中的同名实例变量(根据Jon Skeet)。如果不是这种情况,to 的Body转换MemberExpression将失败,因为MemberExpression表示字段或属性访问。

这个变量提升是否记录了行为,或者它是在其他版本的框架中可能发生变化的实现细节?

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

4

3 回答 3

12

更新:这不再是 C# 6 中的问题,它引入了nameof操作符来解决此类场景(请参阅MSDN)。

看来我的问题的答案是否定的;该功能是非标准化的。情况似乎比我最初怀疑的还要惨淡。不仅捕获变量的提升是非标准化的,而且将匿名函数转换为其表达式树表示的整个规范也是如此。

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

Expression<Func<int, int, int>> add = (int x, int y) => x + y;

以下摘录摘自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# 中的表达式树。

于 2012-06-17T12:25:37.963 回答
4

这是您不应该依赖的行为

看看滥用 C# lambda 表达式或 Syntax brilliance?

现在阅读 C# 设计团队的 Eric Lippert 的评论。他们包括:

我只是问安德斯(和设计团队的其他成员)他们的想法。假设结果无法在适合家庭的报纸上打印

至于为什么这很可怕,我们可以从不明显的、聪明的开始(记住,聪明是不好的,聪明的代码很难维护),根本不在 lambdas 设计者设想的设计用例中,慢,脆弱,不可携带且不必要

从这些陈述中,我会说它不会成为记录或支持的行为。

于 2012-06-16T19:14:30.647 回答
1

AFAIK,这是一个实现细节。

但是,我认为您可以打赌它实际上不会改变。

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

于 2012-06-16T13:15:00.570 回答