如何获取使用局部变量的ConstantExpression的值?

我创建了一个覆盖VisitConstant的ExpressionVisitor实现。 但是,当我创建一个利用局部变量的表达式时,我似乎无法获得变量的实际值。

public class Person { public string FirstName { get; set; } } string name = "Michael"; Expression<Func> exp = p => p.FirstName == name; 

我如何在ConstantExpression中获取变量“name”的值? 我唯一能想到的是:

 string fieldValue = value.GetType().GetFields().First().GetValue(value).ToString(); 

显然,这并不适合非常灵活….

稍微复杂的例子如下:

 Person localPerson = new Person { FirstName = "Michael" }; Expression<Func> exp = p => p.FirstName == localPerson.FirstName; 

以下是我为您列出的两种情况解决的问题。

基本上假设’==’的右侧可以被视为不带参数并返回值的函数,它可以编译为C#委托并调用以检索此值而无需担心代码上的确切内容右手边。

所以基本的示例代码如下

 class Visitor : ExpressionVisitor { protected override Expression VisitBinary( BinaryExpression node ) { var memberLeft = node.Left as MemberExpression; if ( memberLeft != null && memberLeft.Expression is ParameterExpression ) { var f = Expression.Lambda( node.Right ).Compile(); var value = f.DynamicInvoke(); } return base.VisitBinary( node ); } } 

它查找二进制op寻找“arg.member == something”然后只编译/评估右侧,对于这两个例子,你提供的结果是一个字符串“Michael”。

注意,如果您的右侧涉及使用lamda参数,则会失败

p.FirstName == CallSomeFunc(p.FirstName)

编辑:好的,由于AHM的评论,你现在的意思更清楚了。

基本上,代码被编译为在单独的类中捕获name – 然后应用字段访问以从引用它的实例的常量表达式获取其值。 (必须这样做,因为您可以创建表达式更改name的值 – 但表达式捕获变量,而不是值。)

因此,您实际上并不想对VisitConstantConstantExpression执行任何VisitConstant – 您希望在VisitMember进行字段访问。 您需要从ConstantExpression子项中获取值,然后将其提供给FieldInfo以获取值:

 using System; using System.Linq.Expressions; using System.Reflection; public class Person { public string FirstName { get; set; } } static class Program { static void Main(string[] args) { string name = "Michael"; Expression> exp = p => p.FirstName == name; new Visitor().Visit(exp); } } class Visitor : ExpressionVisitor { protected override Expression VisitMember (MemberExpression member) { if (member.Expression is ConstantExpression && member.Member is FieldInfo) { object container = ((ConstantExpression)member.Expression).Value; object value = ((FieldInfo)member.Member).GetValue(container); Console.WriteLine("Got value: {0}", value); } return base.VisitMember(member); } } 

编辑:好的,稍微涉及访问者类的版本:

 class Visitor : ExpressionVisitor { protected override Expression VisitMember (MemberExpression memberExpression) { // Recurse down to see if we can simplify... var expression = Visit(memberExpression.Expression); // If we've ended up with a constant, and it's a property or a field, // we can simplify ourselves to a constant if (expression is ConstantExpression) { object container = ((ConstantExpression) expression).Value; var member = memberExpression.Member; if (member is FieldInfo) { object value = ((FieldInfo)member).GetValue(container); return Expression.Constant(value); } if (member is PropertyInfo) { object value = ((PropertyInfo)member).GetValue(container, null); return Expression.Constant(value); } } return base.VisitMember(memberExpression); } } 

现在运行:

 var localPerson = new Person { FirstName = "Jon" }; Expression> exp = p => p.FirstName == localPerson.FirstName; Console.WriteLine("Before: {0}", exp); Console.WriteLine("After: {0}", new Visitor().Visit(exp)); 

给出结果:

 Before: p => Convert((p.FirstName == value(Program+<>c__DisplayClass1).localPerson.FirstName)) After: p => Convert((p.FirstName == "Jon")) 

通常,您需要使用覆盖VisitConstant和VisitMember来实现自己的ExpressionVisitor,我们还需要一个用于MemberAccess节点的堆栈。

  • 在VisitMember中将节点放在堆栈上
  • 在VisitConstant中创建一个’while循环’来分析前一个节点是否为MemberExpression:
    • 获取上一个节点的Member属性
    • 检测它是否是FieldInfo或PropertyInfo
    • 调用字段/属性信息的GetValue – 它将是您需要的常量值或中间成员的值,可用于在复杂情况下获取下一个值(请参阅下面的内容)
    • 从堆栈中删除MemberExpression
    • 闭环

这样的情况需要循环

 var a = new { new b { c = true; } } var expression = () => abc; 

这是访问常量方法的一部分

  protected override Expression VisitConstant(ConstantExpression node) { MemberExpression prevNode; var val = node.Value; while ((prevNode = PreviousNode as MemberExpression) != null) { var fieldInfo = prevNode.Member as FieldInfo; var propertyInfo = prevNode.Member as PropertyInfo; if (fieldInfo != null) val = fieldInfo.GetValue(val); if (propertyInfo != null) val = propertyInfo.GetValue(val); Nodes.Pop(); } // we got the value // now val = constant we was looking for return node; } 

PreviousNode是执行Stack.Peek的属性

ConstantExpression的问题是编译器使用私有匿名类的对象来存储值lambda被关闭的值,因此常量的值是这个私有类的对象的值。 要访问“实际”常量,您必须分析在ConstantExpression 之前出现的表达式。 过度简化的解决方案可能如下所示:

public sealed class ConstantValueExtractor : ExpressionVisitor { public static object ExtractFirstConstant(Expression expression) { var visitor = new ConstantValueExtractor(); visitor.Visit(expression); return visitor.ConstantValue; } private ConstantValueExtractor() { } private object ConstantValue { get; set; } #region ExpressionVisitor Members public override Expression Visit(Expression node) { this.pathToValue.Push(node); var result = base.Visit(node); this.pathToValue.Pop(); return result; } protected override Expression VisitConstant(ConstantExpression node) { // The first expression in the path is a ConstantExpression node itself, so just skip it. var parentExpression = this.pathToValue.FirstOrDefault( expression => expression.NodeType == ExpressionType.MemberAccess); if (parentExpression != null) { // You might get notable performance overhead here, so consider caching // compiled lambda or use other to extract the value. var valueProviderExpression = Expression.Lambda>( Expression.Convert(parentExpression, typeof(object))); var valueProvider = valueProviderExpression.Compile(); this.ConstantValue = valueProvider(); } return base.VisitConstant(node); } #endregion #region private fields private Stack pathToValue = new Stack(); #endregion } class Test { static void Main() { string name = "Michael"; Expression> exp = p => p.FirstName == name; var value = ConstantValueExtractor.ExtractFirstConstant(exp); Console.WriteLine(value); } }
public sealed class ConstantValueExtractor : ExpressionVisitor { public static object ExtractFirstConstant(Expression expression) { var visitor = new ConstantValueExtractor(); visitor.Visit(expression); return visitor.ConstantValue; } private ConstantValueExtractor() { } private object ConstantValue { get; set; } #region ExpressionVisitor Members public override Expression Visit(Expression node) { this.pathToValue.Push(node); var result = base.Visit(node); this.pathToValue.Pop(); return result; } protected override Expression VisitConstant(ConstantExpression node) { // The first expression in the path is a ConstantExpression node itself, so just skip it. var parentExpression = this.pathToValue.FirstOrDefault( expression => expression.NodeType == ExpressionType.MemberAccess); if (parentExpression != null) { // You might get notable performance overhead here, so consider caching // compiled lambda or use other to extract the value. var valueProviderExpression = Expression.Lambda>( Expression.Convert(parentExpression, typeof(object))); var valueProvider = valueProviderExpression.Compile(); this.ConstantValue = valueProvider(); } return base.VisitConstant(node); } #endregion #region private fields private Stack pathToValue = new Stack(); #endregion } class Test { static void Main() { string name = "Michael"; Expression> exp = p => p.FirstName == name; var value = ConstantValueExtractor.ExtractFirstConstant(exp); Console.WriteLine(value); } } 

我怀疑它是否适用于复杂的表达式,但你应该知道它是如何完成的。

好的,这看起来很有趣。 显然,正在发生的事情是C#将本地堆栈作为常量对象传递,作为表达式的参数。 如果你在你得到的表达式之上添加另一个表达式,比如fx:

 var count = 18; Expression> expr2 = p => p.FirstName == name && count > 10; 

然后你的方法将停止工作 – “name”字段将不再是奇怪的“local-variables”对象中的第一个字段。

我不知道表达式是这样表达的,但似乎你必须使用constantexpression来寻找MemberExpression,因为它是内部表达式。 然后,您可以通过评估该表达式来获取值:

 protected override Expression VisitMember(MemberExpression node) { if (node.Expression.NodeType == ExpressionType.Constant) { var inner = (ConstantExpression)node.Expression; var value = (node.Member as FieldInfo).GetValue(inner.Value); } return base.VisitMember(node); } 

我不知道这是多么可靠,你可能需要更深入地检查元素表达,但是在这里展示的简单示例中,上述方法将起作用。