Come ottenere il valore di un'espressione costante che utilizza una variabile locale?

c# c#-4.0 expression-trees

Domanda

Ho creato un'implementazione di ExpressionVisitor che sovrascrive VisitConstant. Tuttavia, quando creo un'espressione che utilizza una variabile locale, non riesco a ottenere il valore effettivo della variabile.

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

string name = "Michael";

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

In che modo nel mondo ottengo il valore della variabile "nome" da ConstantExpression? L'unica cosa che posso pensare è questa:

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

Ovviamente questo non si presta ad essere molto flessibile anche se ....

Un esempio leggermente più complicato sarebbe il seguente:

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

Risposta accettata

Ecco come l'ho risolto per entrambi i casi che hai elencato.

Sostanzialmente partendo dal presupposto che il lato destro del tuo '==' possa essere trattato come una funzione che non accetta argomenti e restituisce un valore, può essere compilato a un delegato C # e richiamato per recuperare questo valore senza preoccuparsi di esattamente su quale codice il lato destro fa.

Quindi il codice di esempio di base è sotto

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

Cerca un op binario cercando "arg.member == qualcosa", quindi compila / valuta il lato destro, e per entrambi gli esempi il risultato è una stringa "Michael".

Nota, questo fallisce se il tuo lato destro è coinvolto usando l'argomento lamda come

p.FirstName == CallSomeFunc (p.FirstName)


Risposta popolare

EDIT: Ok, è più chiaro cosa intendi ora, grazie al commento di AHM.

Fondamentalmente il codice è compilato per catturare il name in una classe separata e quindi applicare un accesso al campo per ottenere il suo valore dall'espressione costante che fa riferimento a un'istanza di esso. (È necessario farlo perché è possibile modificare il valore del name dopo aver creato l'espressione, ma l'espressione acquisisce la variabile, non il valore.)

Quindi in realtà non vuoi fare nulla su ConstantExpression in VisitConstant - vuoi lavorare sull'accesso al campo in VisitMember . Dovrai ottenere il valore dal figlio di ConstantExpression , quindi assegnarlo a FieldInfo per ottenere il valore:

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: Ok, versione leggermente più coinvolta della classe visitatore:

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

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

Dà il risultato:

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow