Getting ConstantExpression.Value when actual value wrapped into DisplayClass because of closure
Asked Answered
V

2

11

Below is a simple demonstration code of my problem.

[TestClass]
public class ExpressionTests
{
    [TestMethod]
    public void TestParam()
    {
        Search<Student>(s => s.Id == 1L);

        GetStudent(1L);
    }

    private void GetStudent(long id)
    {
        Search<Student>(s => s.Id == id);
    }

    private void Search<T>(Expression<Func<T, bool>> filter)
    {
        var visitor = new MyExpressionVisitor();
        visitor.Visit(filter);
    }
}

public class MyExpressionVisitor : ExpressionVisitor
{
    protected override Expression VisitConstant(ConstantExpression node)
    {
        Assert.AreEqual(1L, node.Value);
        return base.VisitConstant(node);
    }
}

TestParam method causes VisitConstant to be invoked on two different paths:

1. TestParam -> Search -> VisitConstant

In this execution path constant expression (1L) passed to Search method is a real constant value. Here, everything is OK, assert succeeds as expected. When VisitConstant is invoked via first path node.Value.GetType() is Int64 and its .Value is 1L.

2. TestParam -> GetStudent -> Search -> VisitConstant

In this execution path constant expression (id: 1L), is taken by GetStudent as an argument and passed to Search method inside a closure.

Problem

The problem is on the second execution path. When VisitConstant is invoked via second path node.Value.GetType() is MyProject.Tests.ExpressionTests+<>c__DisplayClass0 and this class has a public field named id (same as GetStudent method's argument) which has the value of 1L.

Question

How can I get id value in second path? I know about closures, what a DisplayClass is and why it is created at compile time etc. I am only interested in getting its field value. One thing I can think of is, via reflection. With something like below but it does not seem neat.

node.Value.GetType().GetFields()[0].GetValue(node.Value);

Bonus Problem

While playing with the code for gettting id value I changed VisitConstant method like below (which will not solve my problem though) and get an exception saying "'object' does not contain a definition for 'id'"

enter image description here

Bonus Question

As dynamics are resolved at runtime and DisplayClass is created at compile time, why cannot we access its fields with dynamic? While below code works, I expected that code would work too.

var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
Verona answered 6/9, 2014 at 22:27 Comment(0)
P
4

Here is an article that explains how to do it, and includes code that does it. Basically, what you can do is to create an expression that represents just that subexpression, compile it to a delegate and then execute that delegate. (The article also explains how to identify subexpressions that can be evaluated, but I guess you're not interested in that.)

Using the code from the article, modifying your code to the following will work:

private void Search<T>(Expression<Func<T, bool>> filter)
{
    new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}

As dynamics are resolved at runtime and DisplayClass is created at compile time, why cannot we access its fields with dynamic?

Because that DisplayClass is a private class nested inside ExpressionTests, so code inside MyExpressionVisitor can't access its members.

If you make MyExpressionVisitor a nested class inside ExpressionTests, dynamic will start working on the DisplayClass.

Anonymous types don't behave this way, because they are not emitted as nested private types.

Phthalein answered 11/9, 2014 at 23:6 Comment(0)
C
5

VisitConstant will not help here, as it receives a compiler constructed ConstantExpression that uses object of private anonymous class to store values lambda was closed over (The DisplayClassxxx)

Instead, we should override VisitMember method and inspect its MemberExpression that already has ConstantExpression as an inner Expression.

Here is working test with little reflection.

[TestClass]
public class UnitTest2
{
    [TestMethod]
    public void TestMethod2()
    {
        Search<Student>(s => s.Id == 1L);
        GetStudent(1L);
    }
    private void GetStudent(long id)
    {
        Search<Student>(s => s.Id == id);
    }
    private void Search<T>(Expression<Func<T, bool>> filter)
    {
        var visitor = new MyExpressionVisitor2();
        visitor.Visit(filter.Body);
    }
}

//ExpressionVisitor
public class MyExpressionVisitor2 : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        switch (node.Expression.NodeType)
        {
            case ExpressionType.Constant:
            case ExpressionType.MemberAccess:
            {
                var cleanNode = GetMemberConstant(node);

                //Test
                Assert.AreEqual(1L, cleanNode.Value);

                return cleanNode;
            }
            default:
            {
                return base.VisitMember(node);
            }
        }
    }


    private static ConstantExpression GetMemberConstant(MemberExpression node)
    {
        object value;

        if (node.Member.MemberType == MemberTypes.Field)
        {
            value = GetFieldValue(node);
        }
        else if (node.Member.MemberType == MemberTypes.Property)
        {
            value = GetPropertyValue(node);
        }
        else
        {
            throw new NotSupportedException();
        }

        return Expression.Constant(value, node.Type);
    }
    private static object GetFieldValue(MemberExpression node)
    {
        var fieldInfo = (FieldInfo)node.Member;

        var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;

        return fieldInfo.GetValue(instance);
    }

    private static object GetPropertyValue(MemberExpression node)
    {
        var propertyInfo = (PropertyInfo)node.Member;

        var instance = (node.Expression == null) ? null : TryEvaluate(node.Expression).Value;

        return propertyInfo.GetValue(instance, null);
    }

    private static ConstantExpression TryEvaluate(Expression expression)
    {

        if (expression.NodeType == ExpressionType.Constant)
        {
            return (ConstantExpression)expression;
        }
        throw new NotSupportedException();

    }
}
Circumlocution answered 12/9, 2014 at 4:1 Comment(1)
Nice! This is the fastest way to get the underlying values of a closure, this should be the accepted answer.Satchel
P
4

Here is an article that explains how to do it, and includes code that does it. Basically, what you can do is to create an expression that represents just that subexpression, compile it to a delegate and then execute that delegate. (The article also explains how to identify subexpressions that can be evaluated, but I guess you're not interested in that.)

Using the code from the article, modifying your code to the following will work:

private void Search<T>(Expression<Func<T, bool>> filter)
{
    new MyExpressionVisitor().Visit(Evaluator.PartialEval(filter));
}

As dynamics are resolved at runtime and DisplayClass is created at compile time, why cannot we access its fields with dynamic?

Because that DisplayClass is a private class nested inside ExpressionTests, so code inside MyExpressionVisitor can't access its members.

If you make MyExpressionVisitor a nested class inside ExpressionTests, dynamic will start working on the DisplayClass.

Anonymous types don't behave this way, because they are not emitted as nested private types.

Phthalein answered 11/9, 2014 at 23:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.