Получение ConstantExpression.Value, когда фактическое значение обернуто в DisplayClass из-за закрытия

Ниже приведен простой демонстрационный код моей проблемы.

[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

В этом пути выполнения константное выражение (1L) передается Search Метод является реальной постоянной величиной. Здесь все ок, утверждать удается как положено. когда VisitConstant вызывается по первому пути node.Value.GetType() является Int64 И его .Value является 1L,

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 метод как ниже (который не решит мою проблему все же) и получит исключение, говорящее, что "объект" не содержит определения для "id"

Бонусный вопрос

Как решаются динамики во время выполнения и DisplayClass создается во время компиляции, почему мы не можем получить доступ к его полям с dynamic? Хотя приведенный ниже код работает, я ожидал, что код тоже будет работать.

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

2 ответа

Решение

Вот статья, которая объясняет, как это сделать, и включает код, который это делает. По сути, вы можете создать выражение, представляющее только это подвыражение, скомпилировать его в делегат и затем выполнить этот делегат. (В статье также объясняется, как определить подвыражения, которые можно оценить, но, думаю, вас это не интересует.)

Используя код из статьи, измените ваш код следующим образом:

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

Как решаются динамики во время выполнения и DisplayClass создается во время компиляции, почему мы не можем получить доступ к его полям с dynamic?

Потому что DisplayClass это private класс вложенный внутрь ExpressionTestsтак код внутри MyExpressionVisitor не может получить доступ к своим членам.

Если вы делаете MyExpressionVisitor вложенный класс внутри ExpressionTests, dynamic начнет работать над DisplayClass,

Анонимные типы не ведут себя таким образом, потому что они не выводятся как вложенные private типы.

VisitConstant здесь не поможет, так как получает компилятор ConstantExpression который использует объект частного анонимного класса для хранения значений, лямбда была закрыта (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();

    }
}
Другие вопросы по тегам