How to get the value of a ConstantExpression which uses a local variable?

devlife picture devlife · Aug 9, 2011 · Viewed 9.4k times · Source

I created an ExpressionVisitor implementation that overrides VisitConstant. However, when I create an expression that utilizes a local variable I can't seem to get the actual value of the variable.

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

string name = "Michael";

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

How in the world do I get the value of the variable "name" from the ConstantExpression? The only thing that I can think of is this:

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

Obviously this doesn't lend itself to being very flexible though....

A slightly more complicated example would be the following:

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

Answer

Jon Skeet picture Jon Skeet · Aug 9, 2011

EDIT: Okay, it's clearer what you mean now, thanks to AHM's comment.

Basically the code is compiled to capture name in a separate class - and then apply a field access to get at its value from the constant expression which refers to an instance of it. (It has to do this as you may change the value of name after creating the expression - but the expression captures the variable, not the value.)

So you don't actually want to do anything on the ConstantExpression in VisitConstant - you want to work on the field access in VisitMember. You'll need to get the value from the ConstantExpression child, then give that to the FieldInfo to get the value:

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

EDIT: Okay, slightly more involved version of the visitor class:

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

Now running that with:

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

Gives the result:

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