在运行时通过lambda表达式获取本地变量(和参数)的名称。
在运行时通过lambda表达式获取本地变量(和参数)的名称。
我对以重构安全的方式在运行时检索局部变量(和参数)的名称感兴趣。我有以下扩展方法:
public static string GetVariableName(Expression<Func> 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的说法)。如果不是这种情况,将Body
转换成MemberExpression
会失败,因为MemberExpression
表示字段或属性访问。
这种变量提升是文档化的行为,还是实现细节,可能会在框架的其他版本中发生变化?
注意:这个问题是对我以前有关参数验证的问题的概括。
这是你不应该依赖的行为。
现在读一下C#设计团队成员Eric Lippert的评论。他写道:
我刚刚问安德斯(和设计团队的其他成员)他们的想法。恕我直言,结果没法在适合家庭的报纸上登载。
和
至于为什么这样做是可怕的,我们可以从不明显的、聪明的(请记住,聪明等同于糟糕,聪明的代码很难维护),没有考虑到lambda的设计者设想的使用案例,慢速、脆弱、无法移植和不必要开始说起。
从这些声明中,我会说这不会成为一个被记录或支持的行为。
看起来,我的问题的答案是不可以;该功能不是标准化的。情况比我最初怀疑的还要糟糕,不仅捕获变量的提升没有标准化,而且将匿名函数转换为其表达式树表示的整个规范也没有标准化。
这意味着,即使是直接的匿名函数,例如下面的示例,在框架的不同实现中也不能保证产生一致的表达式树(直到转换被标准化为止):
Expression> add = (int x, int y) => x + y;
以下摘自C# 语言规范4.0(在所有情况下都加粗了强调)。
从“4.6 表达式树类型”:
精确定义泛型类型
Expression
的规则,以及在将匿名函数转换为表达式树类型时构建表达式树的精确规则都不在本规范的范围内,并已在其他地方描述。
来自“6.5.2 对表达式树类型进行匿名函数转换的评估”:
将匿名函数转换为表达式树类型会产生一个表达式树(§4.6)。更精确地说,匿名函数转换的评估导致构建一个表示匿名函数本身结构的对象结构。表达式树的精确结构以及创建它的确切过程是实现定义的。
“6.5.3 实现示例”的第三个示例演示了捕获局部变量的匿名函数的转换,并确认了我提出的问题中提到的变量提升:
局部变量的生命周期现在必须至少扩展到匿名函数委托的生命周期。这可以通过将局部变量“提升”到编译器生成的类的字段中来实现。实例化局部变量(§7.15.5.2)然后对应于创建编译器生成的类的实例,访问局部变量对应于访问编译器生成的类的实例中的字段。
该段落结尾进一步证实了这一点:
当将匿名函数转换为表达式树时,也可以使用此处应用于捕获局部变量的相同技巧:可以将对编译器生成的对象的引用存储在表达式树中,对局部变量的访问可以表示为这些对象上的字段访问。这种方法的优点在于,它允许“提升”的局部变量在委托和表达式树之间共享。
然而,在这个部分的开头有一个免责声明:
在这里所描述的实现基于微软的C#编译器使用的相同原则,但是它并不是强制实施,也不是唯一可能的实现方法。它只简要介绍了将转换为表达式树的过程,因为它们的确切语义超出了本规范的范围。
P.S. Eric Lippert在这条评论中确认表达式树规范从未发布过。DLR文档中有一个Expression Trees v2规范,但它似乎并未涉及将C#中的匿名函数转换为表达式树的过程。