由于闭包,实际值被封装到DisplayClass中,导致获得ConstantExpression.Value

11
以下是我问题的简单演示代码。
[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方法会导致VisitConstant在两个不同的路径上被调用:

1. TestParam -> Search -> VisitConstant

在这个执行路径中,传递给Search方法的常量表达式(1L)是一个真实的常量值。在这里,一切都很好,断言成功。当通过第一个路径调用VisitConstant时,node.Value.GetType()Int64,它的.Value1L

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

在这个执行路径中,常量表达式(id: 1L)被GetStudent作为参数接收,并在闭包内传递给Search方法。

问题

问题出现在第二个执行路径上。当通过第二个路径调用VisitConstant时,node.Value.GetType()MyProject.Tests.ExpressionTests+<>c__DisplayClass0,而这个类有一个名为id的公共字段(与GetStudent方法的参数相同),其值为1L

问题

我该如何在第二个路径中获取id的值?我知道闭包是什么,知道DisplayClass是在编译时创建的等等。我只想获取它的字段值。我能想到的一件事是通过反射来实现。类似下面的代码,但这似乎不太好。

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

奖励问题

在处理获取id值的代码时,我修改了VisitConstant方法如下(虽然这不会解决我的问题),并出现了一个异常,表示"'object' does not contain a definition for 'id'"

enter image description here

奖励问题

由于动态类型是在运行时解析的,而DisplayClass是在编译时创建的,为什么我们不能使用dynamic访问它的字段?尽管下面的代码可以工作,但我期望上面的代码也能工作。

var st = new {Id = 1L};
object o = st;
dynamic dy = o;
Assert.AreEqual(1L, dy.Id);
2个回答

5
`VisitConstant` 方法在这里无法帮助,因为它接收一个编译器构造的 `ConstantExpression`,该表达式使用私有匿名类的对象来存储 lambda 闭合的值(`DisplayClassxxx`)。相反,我们应该重写 `VisitMember` 方法,并检查其 `MemberExpression`,该表达式已经具有 `ConstantExpression` 作为内部表达式。以下是使用少量反射的可行测试。
[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();

    }
}

不错!这是获取闭包底层值的最快方法,应该被接受的答案。 - jorgebg


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接