26

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;
devlife
  • 15,275
  • 27
  • 77
  • 131

6 Answers6

40

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"))
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • No, in this case the value of the expression is of type "UserQuery+<>c__DisplayClass0" and you have to do some kind of inspection on it to get the value. I didn't know that expression behaved in this way, when passed local variables - maybe you can explain what is happening? – AHM Aug 09 '11 at 15:46
  • @AHM, the expression might be accessed outside of the method it was created at, so as all locals might be accesses outside of this method and even outside the lifetime of object the method was called for, so they are converted to members of such private classes. This is the same behaviour you'll see for lambdas. – Konstantin Oznobihin Aug 09 '11 at 15:50
  • 1
    here is a better description of how closures are implemented in C#: http://blogs.msdn.com/b/oldnewthing/archive/2006/08/02/686456.aspx. – Konstantin Oznobihin Aug 09 '11 at 15:55
  • I don't think I have ever seen this behavior with lambdas - normally a lambda just includes paramterexpressions, and then you have to resolve these to evaluate the lambda. But of course you are right, it seems this is the way expression trees represent closures. Maybe I just never inspected an expression tree with a closure in it before :-) – AHM Aug 09 '11 at 16:00
  • I see what you're doing here but it falls apart quickly. For example: Person localPerson = new Person { FirstName = "Michael" }; Then using an expression p => p.FirstName == localPerson.FirstName – devlife Aug 09 '11 at 16:18
  • @devlife In my experience parsing expression trees usually requires at least some expectation about what the expression tree is going to look like. In this case, if you expect a BinarayExpression with person.something on one side, and "something else" on the other hand, then basically you have to add overrides for every kind of "something else" you want you parser to support. So one for plain ConstantExpressions, one for MemberExpressions, one for method calls and so on... – AHM Aug 09 '11 at 16:29
  • @devlife: It doesn't fall apart at all - you asked for the value of the local variable, and that value is the value of `localPerson`. That's what my code already captures. It may not be what you really want, but it's what you asked for. If you're looking for something more sophisticated, you'll have to come up with a more sophisticated description of what you want. – Jon Skeet Aug 09 '11 at 16:31
  • @Jon Skeet, the code you provided does indeed resolve the original code problem. I'm trying to write a framework around a web service call to build up the XML request. I need to be able to parse an expression containing relatively simple BinaryExpression down to MemberExpressions and ConstantExpressions so I can build up the XML appropriately. I need to be able to get down to FirstName == "Michael" from the more complicated example that I just posted. – devlife Aug 09 '11 at 16:38
  • @devlife: Then I suspect you'll need to think a bit more about *exactly* what you need. It won't be simple. For example, would you want to resolve `person.FirstName + "Foo"` to `MichaelFoo`? If so, you'll need to understand how to reduce any expression which doesn't involve parameter expressions, by evaluating it at execution time. This is somewhat harder than the original problem. – Jon Skeet Aug 09 '11 at 16:41
  • @Jon Skeet: I'd love to get to that point but for now I think just being able to handle the complex example would be good enough. I don't anticipate anything more complicated than that (at least for a while). Is there a way to just compile the ConstantExpression to it's end result? Something like Lambda.Expression>()? – devlife Aug 09 '11 at 16:46
  • @devlife: Okay. I'll have a look tonight and see what I can do. I suspect you'll need to basically reduce MemberExpressions to ConstantExpressions whenever you can recursively, but I haven't managed to get it to work yet. – Jon Skeet Aug 09 '11 at 16:56
  • @devlife I added an answer that shows just compiling the right hand side which you could use or adapt if it works in your situations. – MerickOWA Aug 09 '11 at 17:15
  • @Jon Skeet: I definitely appreciate it. – devlife Aug 09 '11 at 17:26
  • @devlife: See my edit at the bottom of my answer. I think that's what you're after, right? – Jon Skeet Aug 09 '11 at 17:32
  • @Joh Skeet: It's in the right direction. I'm working with a lot of generics so that may not work for all scenarios. I'll continue to work on it and will let you know if I find a solution. – devlife Aug 09 '11 at 17:50
  • @devlife: I can't see why generics would muck it up... but feel free to throw another example in to the mix... – Jon Skeet Aug 09 '11 at 17:51
  • @Jon Skeet: I think the issue that was posed has been answered. That being said, I didn't pose the right question. I'll put up another post with the correct code/question. – devlife Aug 09 '11 at 17:57
  • I posted a better question here: http://stackoverflow.com/questions/7000820/how-to-compile-an-expression-down-to-the-actual-result – devlife Aug 09 '11 at 18:16
19

Here's how I solved it for both cases you listed.

Basically assuming that the right hand side of your '==' can be treated like a function that takes no arguments and returns a value, it can be compiled to a C# delegate and invoked to retrieve this value without worrying about exactly what the code on the right hand side does.

So the basic example code is below

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

It looks for a binary op looking for "arg.member == something" then just compiles/evaluates the right hand side, and for both examples your provide the result is a string "Michael".

Note, this fails if your right hand side involved using the lamda argument like

p.FirstName == CallSomeFunc( p.FirstName )

MerickOWA
  • 7,453
  • 1
  • 35
  • 56
  • 1
    not good idea to use Expression.Lambda( node.Right ).Compile(); it is tooooo slow, same for DynamicInvoke :( please see my answer – Serhii Kuzmychov Apr 17 '15 at 19:58
  • 2
    @SergeiKuz'michev I agree, but my answer was focused on flexibility, not speed. With assumptions about the kind of expression that are expected, you can certainly do better than a Compile & DynamicInvoke. – MerickOWA Apr 17 '15 at 20:51
4

In general you need to implement own ExpressionVisitor with overriden VisitConstant and VisitMember, also we need a stack for MemberAccess nodes.

  • in the VisitMember put the node on the stack
  • in the VisitConstant make a 'while loop' to analyze if previous node is MemberExpression:
    • get the Member property of previous node
    • detect if it is FieldInfo or PropertyInfo
    • call GetValue of Field/Property Info - it will be the value of constant you need or value of intermediate member that can be used for obtain next value in complex cases(see bellow)
    • remove MemberExpression from the stack
    • close loop

Loop is needed for cases such this

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

Here is part of visit constant method

    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 is property that do a Stack.Peek

Serhii Kuzmychov
  • 1,186
  • 8
  • 10
1

The issue with ConstantExpression is that compiler put's uses object of private anonymous class to store values lambda was closed over, so the value of the constant is the value of the object of this private class. To access the "actual" constant you'll have to analyze expressions occuring before the ConstantExpression. An overly simplified solution might look like this:


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

I doubt it will work for complicated enough expressions, but you should get the idea of how it could be done.

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • good idea :) but bad implementaion, Compile() is slow an it is not needed – Serhii Kuzmychov Apr 17 '15 at 20:26
  • @SerhiiKuzmychov while I am not sure if it is correct to override the ConstantExpression node, his approach in indeed needed for more complex cases. See this answer: https://stackoverflow.com/a/2616980/ for a more correct implementation. This has the benefit that it evaluates literally every constant expression provided. – nawfal May 11 '21 at 02:17
  • @nawfal no need to use Compile() you already have all you need to get the value via Reflection – Serhii Kuzmychov May 11 '21 at 13:04
  • @SerhiiKuzmychov I think I wasnt clear enough. What I meant is, while MemeberInfo approaches (e.g. Jon Skeet's) work for the given question (i.e. evaluating a _local_ constant), if you want more cases to be covered, `Compile` is the easiest/more comprehensive approach. E.g. Consider this code: `Person person = new Person { Name = "Michael"); Expression> exp = p => p.FirstName == person.Name;`. How would you evaluate the constant `person.Name` using MemberInfo? – nawfal May 13 '21 at 04:10
0

Okay, this seems very interesting. Apparently what is happening is that C# passes the local stackframe as a constant object, as a parameter to your expression. If you add another expression above the one you got, like fx.:

var count = 18;
Expression<Func<Person, object>> expr2 = p => p.FirstName == name && count > 10;

Then your method will stop working - the "name" field will no longer be the first field in the strange "local-variables" object.

I did not know that expressions behaved this way, but it seems that you have to look for at MemberExpression with a constantexpression as it's inner expression. You can then get the value by evaluating that expression:

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

I don't know how reliable this is, you may need to inspect the memberexpression in more depth, but in the simplyfied example you have shown here, the above will work.

AHM
  • 5,145
  • 34
  • 37
0

Here another solution without DynamicInvoke Because the tree is traversed recursively, no stack is requried. If a member of a constant or of a static method is accessed, the value is evaluated after traversing the child nodes.

This replaces expressions like

var myFoo = new Foo{ OtherObjects = new List<OtherObjec>{ new OtherObject {Prop = 5 }};
var expression = () => myFoo.OtherObjects[0].Prop
--->
var expression = () => 5

This method is not complete, and does not handle all complicated cases but it can be a good start for other looking for a solution to this problem

public class ReplaceConstantVisitor : ExpressionVisitor
{
    private int m_memberAccessDepth = 0;
    private object m_constantValue;
    private bool m_constantOnStack = false;

    public ReplaceConstantVisitor()
    {
        m_constantValue = null;
    }

    protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
    {
        var result = base.VisitMethodCall(methodCallExpression);
        if (m_constantOnStack)
        {
            var reducedArguments = methodCallExpression.Arguments.OfType<ConstantExpression>().ToList();
            if (reducedArguments.Count() == methodCallExpression.Arguments.Count)
            {
                m_constantValue = methodCallExpression.Method.Invoke(m_constantValue, reducedArguments.Select(x => x.Value).ToArray());
            }
            else
            {
                m_constantOnStack = false;
            }
        }
        return result;
    }
    
    protected override Expression VisitMember(MemberExpression memberExpression)
    {
        m_memberAccessDepth++;
        var result = base.VisitMember(memberExpression);
        m_memberAccessDepth--;

        // initial condition to do replacement
        switch (memberExpression.Expression)
        {
            // replace constant member access
            case ConstantExpression constantExpression:
                m_constantOnStack = true;
                m_constantValue = constantExpression.Value;
                break;
            // replace static member access
            case null when memberExpression.Member.IsStatic():
                m_constantOnStack = true;
                break;
        }

        if (m_constantOnStack)
        {
            switch (memberExpression.Member)
            {
                case PropertyInfo propertyInfo:
                    m_constantValue = propertyInfo.GetValue(m_constantValue);
                    break;
                case FieldInfo fieldInfo:
                    m_constantValue = fieldInfo.GetValue(m_constantValue);
                    break;
                default:
                    m_constantOnStack = false; // error case abort replacement
                    break;
            }
        }

        if (m_constantOnStack && m_memberAccessDepth == 0)
        {
            m_constantOnStack = false;
            var constant = m_constantValue;
            m_constantValue = null;
            return Expression.Constant(constant);
        }
        return result;
    }
}
Manuel Amstutz
  • 1,311
  • 13
  • 33