Greifen Sie auf den Wert eines Mitgliedsausdrucks zu

70

Wenn ich ein Produkt habe.

var p = new Product { Price = 30 };

und ich habe die folgende linq Abfrage.

var q = repo.Products().Where(x=>x.Price == p.Price).ToList()

In einem IQueryable-Anbieter erhalte ich einen MemberExpression für den p.Price zurück, der einen konstanten Ausdruck enthält, aber ich kann den Wert "30" anscheinend nicht zurückbekommen.

Update Ich habe es versucht, aber es scheint nicht zu funktionieren.

var memberExpression = (MemberExpression)GetRootConstantExpression(m);
var fi = (PropertyInfo)memberExpression.Member;
var val = fi.GetValue(((ConstantExpression)memberExpression.Expression).Value, null);

Prost.

Schotime
quelle

Antworten:

117

Sie können einen Lambda-Ausdruck kompilieren und aufrufen, dessen Hauptteil der Elementzugriff ist:

private object GetValue(MemberExpression member)
{
    var objectMember = Expression.Convert(member, typeof(object));

    var getterLambda = Expression.Lambda<Func<object>>(objectMember);

    var getter = getterLambda.Compile();

    return getter();
}

Die lokale Auswertung ist eine gängige Technik beim Parsen von Ausdrucksbäumen. LINQ to SQL macht genau das an einigen Stellen.

Bryan Watts
quelle
1
Diesen Fehler abrufen Der Ausdruck vom Typ 'System.Double' kann nicht für den Rückgabetyp 'System.Object' verwendet werden, wenn er wie im verwendeten Beispiel in ein Double aufgelöst wird.
Schotime
5
Musste hinzufügen: var expression = Expression.Convert (member, typeof (object)); in der ersten Zeile der Funktion, um den obigen Fehler mit doppelter Konvertierung zu beheben!
Schotime
Ah ja, ich vergesse manchmal, dass Sie bei Ausdrucksbäumen explizit sein müssen, bei denen C # implizit ist (wie bei Konvertierungen). Ich bin froh, dass das für dich funktioniert.
Bryan Watts
Genial !! :) Vielen Dank.
Sebastian
Hinweis: Wenn Sie einen parameterlosen Ausdruck expr vom Typ haben Expression<Func<T>>(der beispielsweise exprden Ausdruck enthält () => myObj), object exprValue = expr.Compile()();führt der Einzeiler den Trick aus. Wirf es anschließend auf den gewünschten Typ. Nützlich in einigen Szenarien.
Matt
35
 MemberExpression right = (MemberExpression)((BinaryExpression)p.Body).Right;
 Expression.Lambda(right).Compile().DynamicInvoke();
Glennular
quelle
Schnellste und präziseste Methode, um das Ergebnis zu erzielen.
Iggymoran
1
Kann nicht glauben, dass etwas, das beinhaltet DynamicInvoke, am schnellsten sein kann @iggymoran hast du es getestet? Oder meintest du am schnellsten zu tippen? ;)
Nawfal
Am schnellsten zu tippen und am einfachsten zu verstehen, was genau los ist. DynamicInvoke verwendet Reflection, um es auszuführen, und ist nicht die schnellste Sache der Welt. Bryan Watts 'Antwort umgeht dieses Problem, indem er eine Funktion erhält und diese ausführt (nur mit einem Aufruf). Als ich zum ersten Mal auf diese Antwort kam, war es einfacher zu verstehen, was los war.
Iggymoran
Ich hätte dir +10 gegeben, wenn ich könnte :) fantastisch
icesar
26

Der konstante Ausdruck verweist auf eine vom Compiler generierte Capture-Klasse. Ich habe die Entscheidungspunkte usw. nicht angegeben, aber hier erfahren Sie, wie Sie 30 daraus erhalten:

var p = new Product { Price = 30 };
Expression<Func<Product, bool>> predicate = x => x.Price == p.Price;
BinaryExpression eq = (BinaryExpression)predicate.Body;
MemberExpression productToPrice = (MemberExpression)eq.Right;
MemberExpression captureToProduct = (MemberExpression)productToPrice.Expression;
ConstantExpression captureConst = (ConstantExpression)captureToProduct.Expression;
object product = ((FieldInfo)captureToProduct.Member).GetValue(captureConst.Value);
object price = ((PropertyInfo)productToPrice.Member).GetValue(product, null);

priceist jetzt 30. Beachten Sie, dass ich davon ausgehe, dass dies Priceeine Eigenschaft ist, aber in Wirklichkeit würden Sie eine GetValueMethode schreiben , die Eigenschaft / Feld behandelt.

Marc Gravell
quelle
Wenn Sie eine andere Verschachtelungsebene im Objekt hätten, würde sich etwas ändern? z.B. p.Product.Price
Schotime
3
@Schotime - in der Tat würde es. Um dies allgemein zu handhaben, siehe Evaluateund TryEvaluatehier: code.google.com/p/protobuf-net/source/browse/trunk/…
Marc Gravell
@MarcGravell Was ist schneller: Kompilieren eines MemberExpressiondann auswertenden oder Erreichen des PropertyInfo/FieldInfodann auswertenden wie in TryEvaluate?
Ashraf Sabry
1
@AshrafSabry, das hängt davon ab, wie oft Sie es tun und ob Sie den Delegierten wiederverwenden
Marc Gravell
3

Die Verwendung Expression.Lambda(myParameterlessExpression).Compile().Invoke()hat mehrere Nachteile:

  • .Compile()ist langsam . Selbst bei kleinen Expressionsfragmenten kann es mehrere Millisekunden dauern, bis der Vorgang abgeschlossen ist. Der InvokeAufruf ist danach jedoch superschnell und benötigt nur wenige Nanosekunden für einfache arithmetische Ausdrücke oder Elementzugriffe.
  • .Compile()generiert (emittiert) MSIL-Code. Das mag perfekt klingen (und erklärt die hervorragende Ausführungsgeschwindigkeit), aber das Problem ist: Dieser Code belegt Speicher, der nicht freigegeben werden kann, bevor die Anwendung beendet ist , selbst wenn der GC die Delegatenreferenz gesammelt hat!

Man kann Compile()diese Probleme entweder ganz vermeiden, um sie zu vermeiden, oder die kompilierten Delegaten zwischenspeichern, um sie wiederzuverwenden. Diese kleine Bibliothek von mir bietet sowohl Interpretation von Expressionsals auch im Cache gespeicherten Zusammenstellung , in der alle Konstanten und Schließungen des Ausdrucks durch zusätzliche Parameter automatisch ersetzt bekommen, die dann in einem Verschluss wieder eingesetzt, die an den Benutzer zurückgegeben wird. Beide Prozesse sind gut getestet, werden in der Produktion eingesetzt, haben Vor- und Nachteile, sind aber weit über 100-mal schneller als Compile()- und vermeiden den Speicherverlust!

Fabian Heimbürger
quelle
2

Wenn Sie eine Klasse hatten:

public class Item
{
    public int Id { get; set; }
}

und eine Instanz des Objekts:

var myItem = new Item { Id = 7 };

Sie können den Wert von Id mithilfe eines Ausdrucks mit dem folgenden Code abrufen:

Expression<Func<Item, int>> exp = x => x.Id;
var me = exp.Body as MemberExpression;
var propInfo = me.Member as PropertyInfo;
var myValue = propInfo.GetValue(myItem, null);

myValue enthält "7"

t_warsop
quelle
1
valueist eine reservierte Kennung
Nick Gallimore
@ NickGallimore danke, guter Ort! Ich habe das Beispiel entsprechend aktualisiert, um dies zu entfernen
t_warsop
1

qist vom Typ List<Product>. Die Liste hat keine Price-Eigenschaft - nur die einzelnen Produkte.

Das erste oder letzte Produkt hat einen Preis.

q.First().Price
q.Last().Price

Wenn Sie wissen, dass es nur eine in der Sammlung gibt, können Sie sie auch mit Single reduzieren

q.Single().Price
Kirk Broadhurst
quelle
Ja, aber .ToList()am Ende macht es in eine Liste.
Kirk Broadhurst
1
Unabhängig davon, ob es sich um eine Liste oder eine IQueryable handelt, können Sie immer noch First, Last oder Single verwenden - aber machen Sie keinen Fehler, repo.Products.ToList()ist definitiv eine Liste
Kirk Broadhurst
1
Sie sind richtig Kobi. Ich weiß alles über dieses Zeug, da ich tatsächlich versuche, den Ausdrucksbaum zu analysieren. Nur ein bisschen komplexer.
Schotime
Ok, ich sehe jetzt, was Sie erreichen wollen / wollten, das habe ich aus der ursprünglichen Frage nicht verstanden.
Kirk Broadhurst
1

Können Sie Folgendes verwenden:

var price = p.Price;
var q = repo.Products().Where(x=>x.Price == price).ToList()
Henk Holterman
quelle
Dies wird funktionieren, aber es wäre großartig, wenn dies nicht passieren müsste. Unterstützt Linq-2-Sql die Syntax, die ich erreichen möchte?
Schotime
0

Und was genau versuchst du zu erreichen?

Denn um auf den Wert von zuzugreifen Price, müssten Sie Folgendes tun:

var valueOfPrice = q[0].Price;
Paulo Santos
quelle
Ich versuche, den Ausdruck in Klartext zu übergeben, und muss den p.Preis in "30"
auswerten
-1

Ab 2020

Diese Hilfsmethode ruft jeden Ausdruckswert ordnungsgemäß ab, ohne "Hack zu kompilieren":

public static object GetMemberExpressionValue (MemberExpression expression)
{
    // Dependency chain of a MemberExpression is of the form:
    // MemberExpression expression
    //    MemberExpression expression.Expression
    //        ... MemberExpression expression.[...].Expression
    //            ConstantExpression expression.[...].Expression.Expression <- base object
    var dependencyChain = new List<MemberExpression>();
    var pointingExpression = expression;
    while (pointingExpression != null)
    {
        dependencyChain.Add(pointingExpression);
        pointingExpression = pointingExpression.Expression as MemberExpression;
    }

    if (!(dependencyChain.Last().Expression is ConstantExpression baseExpression))
    {
        throw new Exception(
            $"Last expression {dependencyChain.Last().Expression} of dependency chain of {expression} is not a constant." +
            "Thus the expression value cannot be found.");
    }

    var resolvedValue = baseExpression.Value;

    for (var i = dependencyChain.Count; i > 0; i--)
    {
        var expr = dependencyChain[i - 1];
        resolvedValue = new PropOrField(expr.Member).GetValue(resolvedValue);
    }

    return resolvedValue;
}

PropOrField Klasse:

public class PropOrField
{
    public readonly MemberInfo MemberInfo;

    public PropOrField (MemberInfo memberInfo)
    {
        if (!(memberInfo is PropertyInfo) && !(memberInfo is FieldInfo))
        {
            throw new Exception(
                $"{nameof(memberInfo)} must either be {nameof(PropertyInfo)} or {nameof(FieldInfo)}");
        }

        MemberInfo = memberInfo;
    }

    public object GetValue (object source)
    {
        if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.GetValue(source);
        if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.GetValue(source);

        return null;
    }

    public void SetValue (object target, object source)
    {
        if (MemberInfo is PropertyInfo propertyInfo) propertyInfo.SetValue(target, source);
        if (MemberInfo is FieldInfo fieldInfo) fieldInfo.SetValue(target, source);
    }

    public Type GetMemberType ()
    {
        if (MemberInfo is PropertyInfo propertyInfo) return propertyInfo.PropertyType;
        if (MemberInfo is FieldInfo fieldInfo) return fieldInfo.FieldType;

        return null;
    }
}
Alexandre Daubricourt
quelle