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) 是一个实常量值。到这里,一切正常,assert按预期成功。当VisitConstant通过第一个路径调用时node.Value.GetType()isInt64和它的.Valueis 1L

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

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

问题

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

问题

如何id在第二条路径中获得价值?我知道闭包,什么是 aDisplayClass以及为什么在编译时创建它等。我只对获取它的字段值感兴趣。我能想到的一件事是,通过反射。像下面这样的东西,但它看起来并不整洁。

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

奖金问题

在使用获取id值的代码时,我更改了VisitConstant如下方法(但这不会解决我的问题)并得到一个异常说“'object'不包含'id'的定义”

在此处输入图像描述

奖金问题

由于动态是在运行时解析并DisplayClass在编译时创建的,为什么我们不能使用 访问它的字段dynamic?虽然下面的代码可以工作,但我希望该代码也可以工作。

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

2 回答 2

5

VisitConstant在这里无济于事,因为它接收构造的编译器,该编译器ConstantExpression使用私有匿名类的对象来存储值 lambda 被关闭 (The DisplayClassxxx)

相反,我们应该重写VisitMember方法并检查它MemberExpression已经ConstantExpression作为内部的方法Expression

这是几乎没有反思的工作测试。

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

    }
}
于 2014-09-12T04:01:34.353 回答
4

这是一篇解释如何执行此操作的文章,并包含执行此操作的代码。基本上,您可以做的是创建一个仅表示该子表达式的表达式,将其编译为委托,然后执行该委托。(这篇文章还解释了如何识别可以评估的子表达式,但我猜你对此不感兴趣。)

使用文章中的代码,将您的代码修改为以下内容即可:

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

由于动态是在运行时解析并DisplayClass在编译时创建的,为什么我们不能使用 访问它的字段dynamic

因为那DisplayClass是一个private嵌套在里面的类ExpressionTests,所以里面的代码MyExpressionVisitor不能访问它的成员。

如果你MyExpressionVisitor在里面做了一个嵌套类ExpressionTestsdynamic就会开始工作了DisplayClass

匿名类型不会以这种方式运行,因为它们不是作为嵌套private类型发出的。

于 2014-09-11T23:06:25.143 回答