Wie bekomme ich den Wert einer ConstantExpression, die eine lokale Variable verwendet?

c# c#-4.0 expression-trees

Frage

Ich habe eine ExpressionVisitor-Implementierung erstellt, die VisitConstant überschreibt. Wenn ich jedoch einen Ausdruck erzeuge, der eine lokale Variable verwendet, kann ich nicht den tatsächlichen Wert der Variablen erhalten.

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

string name = "Michael";

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

Wie in der Welt bekomme ich den Wert der Variablen "name" von der ConstantExpression? Das einzige, was mir einfällt, ist folgendes:

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

Offensichtlich ist dies jedoch nicht sehr flexibel.

Ein etwas komplizierteres Beispiel wäre das Folgende:

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

Akzeptierte Antwort

Hier ist, wie ich es für beide Fälle, die Sie aufgelistet haben, gelöst habe.

Wenn Sie davon ausgehen, dass die rechte Seite Ihres '==' wie eine Funktion behandelt werden kann, die keine Argumente annimmt und einen Wert zurückgibt, kann sie in einen C # -Delegaten kompiliert und aufgerufen werden, ohne dass Sie genau darüber nachdenken müssen, woran der Code liegt die rechte Seite tut es.

Also der grundlegende Beispielcode ist unten

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

Es sucht nach einem binären Op, der nach "arg.member == something" sucht, dann kompiliert / wertet er einfach die rechte Seite aus, und für beide Beispiele liefert das Ergebnis eine Zeichenfolge "Michael".

Beachten Sie, dass dies fehlschlägt, wenn Ihre rechte Seite das lamda-Argument verwendet

p.FirstName == CallSomeFunc (p.FirstName)


Beliebte Antwort

EDIT: Okay, es ist klarer, was du jetzt meinst, dank AHMs Kommentar.

Grundsätzlich wird der Code kompiliert, um den name in einer separaten Klasse zu erfassen - und dann einen Feldzugriff anzuwenden, um seinen Wert aus dem konstanten Ausdruck zu erhalten, der auf eine Instanz davon verweist. (Dazu müssen Sie den Wert des name nach dem Erstellen des Ausdrucks ändern. Der Ausdruck erfasst jedoch die Variable und nicht den Wert.)

Daher möchten Sie eigentlich nichts in ConstantExpression in VisitConstant - Sie möchten an dem VisitMember in VisitMember . Sie müssen den Wert vom ConstantExpression Kind FieldInfo und dann an die FieldInfo , um den Wert zu erhalten:

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: Okay, etwas mehr beteiligte Version der Besucherklasse:

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

Jetzt läuft das mit:

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

Gibt das Ergebnis:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow