¿Cómo obtener el valor de una expresión constante que utiliza una variable local?

c# c#-4.0 expression-trees

Pregunta

Creé una implementación de ExpressionVisitor que reemplaza VisitConstant. Sin embargo, cuando creo una expresión que utiliza una variable local, parece que no puedo obtener el valor real de la variable.

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

string name = "Michael";

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

¿Cómo en el mundo obtengo el valor de la variable "nombre" de ConstantExpression? Lo único que puedo pensar es esto:

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

Obviamente, esto no se presta a ser muy flexible, aunque ...

Un ejemplo un poco más complicado sería el siguiente:

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

Respuesta aceptada

Así es como lo resolví para los dos casos que mencionaste.

Básicamente, asumiendo que el lado derecho de su '==' puede tratarse como una función que no toma argumentos y devuelve un valor, puede compilarse en un delegado de C # e invocarse para recuperar este valor sin preocuparse por lo que indica exactamente el código en el lado derecho hace

Así que el código de ejemplo básico está debajo

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

Busca una operación binaria que busca "arg.member == algo", luego simplemente compila / evalúa el lado derecho, y para ambos ejemplos, el resultado es una cadena "Michael".

Tenga en cuenta que esto falla si su lado derecho involucró el uso del argumento lamda como

p.FirstName == CallSomeFunc (p.FirstName)


Respuesta popular

EDIT: De acuerdo, está más claro lo que quiere decir ahora, gracias al comentario de AHM.

Básicamente, el código se compila para capturar el name en una clase separada, y luego se aplica un acceso de campo para obtener su valor de la expresión constante que se refiere a una instancia de este. (Tiene que hacer esto, ya que puede cambiar el valor del name después de crear la expresión, pero la expresión captura la variable, no el valor).

Por lo tanto, realmente no desea hacer nada en ConstantExpression en VisitConstant ; desea trabajar en el acceso de campo en VisitMember . Necesitará obtener el valor del hijo de ConstantExpression , y luego dárselo a FieldInfo para obtener el valor:

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

EDITAR: Muy bien, la versión un poco más complicada de la clase visitante:

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

Ahora ejecutando eso con:

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

Da el resultado:

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


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow