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

c# c#-4.0 expression-trees

Вопрос

Я создал реализацию ExpressionVisitor, которая переопределяет VisitConstant. Однако, когда я создаю выражение, которое использует локальную переменную, я не могу получить фактическое значение переменной.

public class Person
{
  public string FirstName { get; set; }
}

string name = "Michael";

Expression<Func<Person, object>> exp = p => p.FirstName == name;

Как в мире получить значение переменной «name» из ConstantExpression? Единственное, что я могу придумать:

string fieldValue = value.GetType().GetFields().First().GetValue(value).ToString();

Очевидно, это не поддается гибкости, хотя ....

Немного более сложный пример:

Person localPerson = new Person { FirstName = "Michael" };
Expression<Func<Person, object>> exp = p => p.FirstName == localPerson.FirstName;

Принятый ответ

Вот как я решил это для обоих случаев, которые вы указали.

В принципе, предполагая, что правая сторона вашего '==' может рассматриваться как функция, которая не принимает аргументов и возвращает значение, ее можно скомпилировать в делегат C # и вызвать для получения этого значения, не беспокоясь о том, что именно код правая сторона делает.

Таким образом, базовый примерный код ниже

class Visitor : ExpressionVisitor {

  protected override Expression VisitBinary( BinaryExpression node ) {

    var memberLeft = node.Left as MemberExpression;
    if ( memberLeft != null && memberLeft.Expression is ParameterExpression ) {

      var f = Expression.Lambda( node.Right ).Compile();
      var value = f.DynamicInvoke();
      }

    return base.VisitBinary( node );
    }
  }

Он ищет двоичный op, который ищет «arg.member == something», а затем просто компилирует / оценивает правую сторону, и для обоих примеров ваш результат дает строку «Michael».

Обратите внимание: это не сработает, если ваша правая сторона задействовала аргумент lamda, например

p.FirstName == CallSomeFunc (p.FirstName)


Популярные ответы

EDIT: Хорошо, теперь понятно, что вы имеете в виду, благодаря комментарию AHM.

В основном код скомпилирован для захвата name в отдельном классе, а затем применить доступ к полю для получения его значения из выражения константы, которое ссылается на его экземпляр. (Он должен сделать это, так как вы можете изменить значение name после создания выражения, но выражение захватывает переменную, а не значение.)

Таким образом, вы фактически не хотите ничего делать в ConstantExpression в VisitConstant - вы хотите работать с доступом к полю в VisitMember . Вам нужно будет получить значение из дочернего FieldInfo ConstantExpression , а затем передать его в поле FieldInfo чтобы получить значение:

using System;
using System.Linq.Expressions;
using System.Reflection;

public class Person
{
    public string FirstName { get; set; }
}

static class Program
{
    static void Main(string[] args)
    {
        string name = "Michael";

        Expression<Func<Person, object>> exp = p => p.FirstName == name;

        new Visitor().Visit(exp);
    }
}

class Visitor : ExpressionVisitor    
{
    protected override Expression VisitMember
        (MemberExpression member)
    {
        if (member.Expression is ConstantExpression &&
            member.Member is FieldInfo)
        {
            object container = 
                ((ConstantExpression)member.Expression).Value;
            object value = ((FieldInfo)member.Member).GetValue(container);
            Console.WriteLine("Got value: {0}", value);
        }
        return base.VisitMember(member);
    }
}

EDIT: Хорошо, немного более привлекательная версия класса посетителей:

class Visitor : ExpressionVisitor    
{
    protected override Expression VisitMember
        (MemberExpression memberExpression)
    {
        // Recurse down to see if we can simplify...
        var expression = Visit(memberExpression.Expression);

        // If we've ended up with a constant, and it's a property or a field,
        // we can simplify ourselves to a constant
        if (expression is ConstantExpression)
        {
            object container = ((ConstantExpression) expression).Value;
            var member = memberExpression.Member;
            if (member is FieldInfo)
            {
                object value = ((FieldInfo)member).GetValue(container);
                return Expression.Constant(value);
            }
            if (member is PropertyInfo)
            {
                object value = ((PropertyInfo)member).GetValue(container, null);
                return Expression.Constant(value);
            }
        }
        return base.VisitMember(memberExpression);
    }
}

Теперь выполните это с помощью:

var localPerson = new Person { FirstName = "Jon" };

Expression<Func<Person, object>> exp = p => p.FirstName == localPerson.FirstName;

Console.WriteLine("Before: {0}", exp);
Console.WriteLine("After: {0}", new Visitor().Visit(exp));

Дает результат:

Before: p => Convert((p.FirstName == 
           value(Program+<>c__DisplayClass1).localPerson.FirstName))
After: p => Convert((p.FirstName == "Jon"))


Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow