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

时间:2011-08-09 15:15:41

标签: c# c#-4.0 expression-trees

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

public class Person
{
  public string FirstName { get; set; }
}

string name = "Michael";

Expression<Func<Person, object>> exp = p => p.FirstName == name;

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

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

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

稍微复杂的例子如下:

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

5 个答案:

答案 0 :(得分:31)

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

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

因此,您实际上并不想对ConstantExpression中的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<Func<Person, object>> 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<Func<Person, object>> 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"))

答案 1 :(得分:16)

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

基本上假设'=='的右侧可以被视为不带参数并返回值的函数,它可以编译为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)

答案 2 :(得分:2)

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

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

这种情况需要循环

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

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

    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

的属性

答案 3 :(得分:0)

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

var count = 18;
Expression<Func<Person, object>> 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);
}

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

答案 4 :(得分:0)

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


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);
    }
}

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