C# Language
Ausdrucksbäume
Suche…
Einführung
Syntax
- Ausdruck <TDelegate> name = lambdaExpression;
Parameter
Parameter | Einzelheiten |
---|---|
TDelegate | Der Delegattyp, der für den Ausdruck verwendet werden soll |
LambdaExpression | Der Lambda-Ausdruck (zB num => num < 5 ) |
Bemerkungen
Intro zu Ausdrucksbäumen
Woher wir kamen
In Ausdrucksbäumen wird zur Laufzeit "Quellcode" verwendet. decimal CalculateTotalTaxDue(SalesOrder order)
Sie sich eine Methode vor, die die auf eine decimal CalculateTotalTaxDue(SalesOrder order)
fällige Umsatzsteuer decimal CalculateTotalTaxDue(SalesOrder order)
. Die Verwendung dieser Methode in einem .NET-Programm ist einfach - Sie nennen es einfach decimal taxDue = CalculateTotalTaxDue(order);
. Was ist, wenn Sie es auf alle Ergebnisse einer Remote-Abfrage anwenden möchten (SQL, XML, Remote-Server usw.)? Diese entfernten Abfragequellen können die Methode nicht aufrufen! In all diesen Fällen müssten Sie normalerweise den Fluss umkehren. Machen Sie die gesamte Abfrage, speichern Sie sie im Arbeitsspeicher, durchlaufen Sie die Ergebnisse und berechnen Sie die Steuer für jedes Ergebnis.
So vermeiden Sie die Speicher- und Latenzprobleme der Flussumkehrung
Ausdrucksbäume sind Datenstrukturen in einem Format eines Baums, wobei jeder Knoten einen Ausdruck enthält. Sie werden verwendet, um die kompilierten Anweisungen (wie zum Filtern von Daten verwendete Methoden) in Ausdrücke zu übersetzen, die außerhalb der Programmumgebung verwendet werden könnten, beispielsweise innerhalb einer Datenbankabfrage.
Das Problem hierbei ist, dass eine Remote-Abfrage nicht auf unsere Methode zugreifen kann . Wir könnten dieses Problem vermeiden, wenn wir stattdessen die Anweisungen für die Methode an die Fernabfrage senden. In unserem CalculateTotalTaxDue
Beispiel bedeutet das, dass wir diese Informationen senden:
- Erstellen Sie eine Variable, um die Gesamtsteuer zu speichern
- Durchlaufen Sie alle Zeilen der Bestellung
- Prüfen Sie für jede Zeile, ob das Produkt steuerpflichtig ist
- Ist dies der Fall, multiplizieren Sie die Zeilensumme mit dem anwendbaren Steuersatz und addieren Sie diesen Betrag zur Gesamtsumme
- Sonst nichts tun
Mit diesen Anweisungen kann die Remote-Abfrage die Arbeit ausführen, während sie die Daten erstellt.
Es gibt zwei Herausforderungen, um dies umzusetzen. Wie wandeln Sie eine kompilierte .NET-Methode in eine Liste von Anweisungen um und wie formatieren Sie die Anweisungen so, dass sie vom Remote-System verwendet werden können?
Ohne Ausdrucksbäume konnten Sie nur das erste Problem mit MSIL lösen. (MSIL ist der Assembler-artige Code, der vom .NET-Compiler erstellt wird.) Das Analysieren von MSIL ist möglich , aber nicht einfach. Selbst wenn Sie es richtig analysieren, kann es schwierig sein, die Absicht des ursprünglichen Programmierers mit einer bestimmten Routine zu bestimmen.
Ausdrucksbäume retten den Tag
Ausdrucksbäume behandeln genau diese Probleme. Sie stellen Programmanweisungen in einer Baumdatenstruktur dar, wobei jeder Knoten eine Anweisung darstellt und Verweise auf alle Informationen enthält, die Sie zur Ausführung dieser Anweisung benötigen. EineMethodCallExpression
hat beispielsweise einen Verweis auf 1) die MethodInfo
, die aufgerufen werden soll, 2) eine Liste von Expression
, die an diese Methode übergeben wird, 3) für Instanzmethoden und den Expression
, für den Sie die Methode aufrufen. Sie können "durch den Baum gehen" und die Anweisungen auf Ihre Remote-Abfrage anwenden. Ausdrucksbäume erstellen
Die einfachste Möglichkeit zum Erstellen eines Ausdrucksbaums besteht in einem Lambda-Ausdruck. Diese Ausdrücke sehen fast genauso aus wie normale C # -Methoden. Es ist wichtig zu wissen, dass dies Compiler-Magie ist . Wenn Sie zum ersten Mal einen Lambda-Ausdruck erstellen, prüft der Compiler, wozu Sie ihn zuweisen. Wenn es ein istDelegate
- Typ (einschließlich Action
oder Func
), wandelt der Compiler den Lambda - Ausdruck in einen Delegierten. Wenn es sich um eine LambdaExpression
(oder um einen Expression<Action<T>>
oder einen Expression<Func<T>>
dem es sich stark um LambdaExpression
- LambdaExpression
), wandelt der Compiler ihn in eine LambdaExpression
. Hier setzt die Magie an. Hinter den Kulissen verwendet der Compiler die Ausdrucksstruktur-API, um Ihren Lambda-Ausdruck in eine LambdaExpression
. Lambda-Ausdrücke können nicht jede Art von Ausdrucksbaum erstellen. In diesen Fällen können Sie die Expressions-API manuell verwenden, um die Baumstruktur zu erstellen, die Sie benötigen. Im Beispiel für das Verständnis der Ausdrücke-API erstellen wir den CalculateTotalSalesTax
Ausdruck mithilfe der API.
HINWEIS: Die Namen werden hier etwas verwirrend. Ein Lambda-Ausdruck (zwei Wörter, Kleinbuchstaben) bezieht sich auf den Codeblock mit einem Indikator =>
. Es stellt eine anonyme Methode in C # dar und wird entweder in einen Delegate
oder einen Expression
konvertiert. Eine LambdaExpression
(ein Wort, PascalCase) bezieht sich auf den Knotentyp innerhalb der Ausdruck-API, der eine Methode darstellt, die Sie ausführen können.
Ausdrucksbäume und LINQ
Eine der häufigsten Anwendungen von Ausdrucksbäumen ist bei LINQ- und Datenbankabfragen. LINQ paart eine Ausdrucksbaumstruktur mit einem Abfrageanbieter, um Ihre Anweisungen auf die Remote-Zielabfrage anzuwenden. Beispielsweise wandelt der Abfrage-Provider LINQ to Entity Framework einen Ausdrucksbaum in SQL um, der direkt für die Datenbank ausgeführt wird.
Wenn Sie alle Teile zusammenfügen, können Sie die wahre Stärke von LINQ erkennen.
- Schreiben Sie eine Abfrage mit einem Lambda-Ausdruck:
products.Where(x => x.Cost > 5)
- Der Compiler wandelt diesen Ausdruck in einen Ausdrucksbaum mit den Anweisungen "prüfe, ob die Cost-Eigenschaft des Parameters größer als fünf ist".
- Der Abfrageanbieter analysiert die Ausdrucksbaumstruktur und erstellt eine gültige SQL-Abfrage
SELECT * FROM products WHERE Cost > 5
- Der ORM projiziert alle Ergebnisse in POCOs und Sie erhalten eine Liste der Objekte zurück
Anmerkungen
- Ausdrucksbäume sind unveränderlich. Wenn Sie einen Ausdrucksbaum ändern möchten, müssen Sie einen neuen erstellen, den vorhandenen in den neuen kopieren (um einen Ausdrucksbaum zu durchsuchen, können Sie den
ExpressionVisitor
) und die gewünschten Änderungen vornehmen.
Erstellen von Ausdrucksbäumen mithilfe der API
using System.Linq.Expressions;
// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });
Ausdrucksbäume kompilieren
// Define an expression tree, taking an integer, returning a bool.
Expression<Func<int, bool>> expr = num => num < 5;
// Call the Compile method on the expression tree to return a delegate that can be called.
Func<int, bool> result = expr.Compile();
// Invoke the delegate and write the result to the console.
Console.WriteLine(result(4)); // Prints true
// Prints True.
// You can also combine the compile step with the call/invoke step as below:
Console.WriteLine(expr.Compile()(4));
Analysieren von Ausdrucksbäumen
using System.Linq.Expressions;
// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
param.Name, left.Name, operation.NodeType, right.Value);
// Decomposed expression: num => num LessThan 5
Erstellen Sie Ausdrucksbäume mit einem Lambda-Ausdruck
Es folgt der grundlegendste Ausdrucksbaum, der von Lambda erstellt wird.
Expression<Func<int, bool>> lambda = num => num == 42;
Um Ausdrucksbäume "von Hand" zu erstellen, sollte man die Expression
Klasse verwenden.
Der obige Ausdruck wäre äquivalent zu:
ParameterExpression parameter = Expression.Parameter(typeof(int), "num"); // num argument
ConstantExpression constant = Expression.Constant(42, typeof(int)); // 42 constant
BinaryExpression equality = Expression.Equals(parameter, constant); // equality of two expressions (num == 42)
Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(equality, parameter);
Grundlegendes zur API für Ausdrücke
Wir werden die Ausdrucksstruktur-API verwenden, um eine CalculateSalesTax
Struktur zu erstellen. Im Klartext finden Sie hier eine Zusammenfassung der Schritte, die zum Erstellen des Baums erforderlich sind.
- Prüfen Sie, ob das Produkt steuerpflichtig ist
- Ist dies der Fall, multiplizieren Sie die Zeilensumme mit dem anwendbaren Steuersatz und geben Sie diesen Betrag zurück
- Ansonsten 0 zurückgeben
//For reference, we're using the API to build this lambda expression
orderLine => orderLine.IsTaxable ? orderLine.Total * orderLine.Order.TaxRate : 0;
//The orderLine parameter we pass in to the method. We specify it's type (OrderLine) and the name of the parameter.
ParameterExpression orderLine = Expression.Parameter(typeof(OrderLine), "orderLine");
//Check if the parameter is taxable; First we need to access the is taxable property, then check if it's true
PropertyInfo isTaxableAccessor = typeof(OrderLine).GetProperty("IsTaxable");
MemberExpression getIsTaxable = Expression.MakeMemberAccess(orderLine, isTaxableAccessor);
UnaryExpression isLineTaxable = Expression.IsTrue(getIsTaxable);
//Before creating the if, we need to create the braches
//If the line is taxable, we'll return the total times the tax rate; get the total and tax rate, then multiply
//Get the total
PropertyInfo totalAccessor = typeof(OrderLine).GetProperty("Total");
MemberExpression getTotal = Expression.MakeMemberAccess(orderLine, totalAccessor);
//Get the order
PropertyInfo orderAccessor = typeof(OrderLine).GetProperty("Order");
MemberExpression getOrder = Expression.MakeMemberAccess(orderLine, orderAccessor);
//Get the tax rate - notice that we pass the getOrder expression directly to the member access
PropertyInfo taxRateAccessor = typeof(Order).GetProperty("TaxRate");
MemberExpression getTaxRate = Expression.MakeMemberAccess(getOrder, taxRateAccessor);
//Multiply the two - notice we pass the two operand expressions directly to multiply
BinaryExpression multiplyTotalByRate = Expression.Multiply(getTotal, getTaxRate);
//If the line is not taxable, we'll return a constant value - 0.0 (decimal)
ConstantExpression zero = Expression.Constant(0M);
//Create the actual if check and branches
ConditionalExpression ifTaxableTernary = Expression.Condition(isLineTaxable, multiplyTotalByRate, zero);
//Wrap the whole thing up in a "method" - a LambdaExpression
Expression<Func<OrderLine, decimal>> method = Expression.Lambda<Func<OrderLine, decimal>>(ifTaxableTernary, orderLine);
Ausdrucksbaum Basic
Ausdrucksbäume repräsentieren Code in einer baumartigen Datenstruktur, wobei jeder Knoten ein Ausdruck ist
Expression Trees ermöglicht die dynamische Änderung von ausführbarem Code, die Ausführung von LINQ-Abfragen in verschiedenen Datenbanken und die Erstellung dynamischer Abfragen. Sie können Code, der durch Ausdrucksbäume dargestellt wird, kompilieren und ausführen.
Diese werden auch in der Dynamic Language Run-Time (DLR) verwendet, um Interoperabilität zwischen dynamischen Sprachen und .NET Framework bereitzustellen und Compiler-Autoren zu ermöglichen, Ausdrucksbäume anstelle von Microsoft Intermediate Language (MSIL) auszugeben.
Ausdrucksbäume können über erstellt werden
- Anonymer Lambda-Ausdruck,
- Manuell unter Verwendung des System.Linq.Expressions-Namespace.
Ausdrucksbäume aus Lambda-Ausdrücken
Wenn der Ausdruckstypvariable ein Lambda-Ausdruck zugewiesen wird, gibt der Compiler Code aus, um einen Ausdrucksbaum zu erstellen, der den Lambda-Ausdruck darstellt.
Die folgenden Codebeispiele zeigen, wie der C # -Compiler eine Ausdrucksbaumstruktur erstellt, die den Lambda-Ausdruck num => num <5 darstellt.
Expression<Func<int, bool>> lambda = num => num < 5;
Ausdrucksbäume mithilfe der API
Ausdrucksbäume werden auch mit der Ausdrucksklasse erstellt . Diese Klasse enthält statische Factory-Methoden, die Ausdrucksstrukturknoten bestimmter Typen erstellen.
Nachfolgend finden Sie einige Baumknoten.
- ParameterExpression
- MethodCallExpression
Das folgende Codebeispiel zeigt, wie Sie mithilfe der API eine Ausdrucksbaumstruktur erstellen, die den Lambda-Ausdruck num => num <5 darstellt.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 = Expression.Lambda<Func<int, bool>>(numLessThanFive,new ParameterExpression[] { numParam });
Untersuchung der Struktur eines Ausdrucks mithilfe von Besucher
Definieren Sie eine neue Besucherklasse, indem Sie einige der Methoden von ExpressionVisitor überschreiben:
class PrintingVisitor : ExpressionVisitor {
protected override Expression VisitConstant(ConstantExpression node) {
Console.WriteLine("Constant: {0}", node);
return base.VisitConstant(node);
}
protected override Expression VisitParameter(ParameterExpression node) {
Console.WriteLine("Parameter: {0}", node);
return base.VisitParameter(node);
}
protected override Expression VisitBinary(BinaryExpression node) {
Console.WriteLine("Binary with operator {0}", node.NodeType);
return base.VisitBinary(node);
}
}
Rufen Sie Visit
an, um diesen Besucher für einen vorhandenen Ausdruck zu verwenden:
Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);