简体   繁体   中英

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

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'"

在此输入图像描述

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

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.

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

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM