C# Language
Uitdrukkingsbomen
Zoeken…
Invoering
Syntaxis
- Expression <TDelegate> name = lambdaExpression;
parameters
Parameter | Details |
---|---|
TDelegate | Het gemachtigde type dat moet worden gebruikt voor de uitdrukking |
lambdaExpression | De lambda-expressie (bijv. num => num < 5 ) |
Opmerkingen
Inleiding tot expressiebomen
Waar we vandaan kwamen
Uitdrukkingsbomen gaan allemaal over het consumeren van "broncode" tijdens runtime. Overweeg een methode die de verschuldigde omzetbelasting berekent op een decimal CalculateTotalTaxDue(SalesOrder order)
verkooporder decimal CalculateTotalTaxDue(SalesOrder order)
. Het gebruik van die methode in een .NET-programma is eenvoudig - u noemt het gewoon decimal taxDue = CalculateTotalTaxDue(order);
. Wat als u het wilt toepassen op alle resultaten van een externe query (SQL, XML, een externe server, enz.)? Die externe bronbronnen kunnen de methode niet aanroepen! Traditioneel zou je de stroom in al deze gevallen moeten omkeren. Voer de hele zoekopdracht uit, sla deze op in het geheugen, loop dan door de resultaten en bereken de belasting voor elk resultaat.
Hoe geheugen- en latentieproblemen van stroominversie te voorkomen
Expressiebomen zijn gegevensstructuren in een boomstructuur, waarbij elke knoop een expressie bevat. Ze worden gebruikt om de gecompileerde instructies (zoals methoden die worden gebruikt om gegevens te filteren) te vertalen in uitdrukkingen die buiten de programmeeromgeving kunnen worden gebruikt, zoals in een databasequery.
Het probleem is dat een externe zoekopdracht geen toegang heeft tot onze methode . We kunnen dit probleem voorkomen als we in plaats daarvan de instructies voor de methode naar de externe query verzenden. In ons voorbeeld CalculateTotalTaxDue
betekent dit dat we deze informatie verzenden:
- Maak een variabele om de totale belasting op te slaan
- Loop door alle regels van de bestelling
- Controleer voor elke regel of het product belastbaar is
- Als dit het geval is, vermenigvuldigt u het regeltotaal met het toepasselijke belastingtarief en telt u dat bedrag op bij het totaal
- Anders niets doen
Met die instructies kan de externe query het werk uitvoeren terwijl de gegevens worden gemaakt.
Er zijn twee uitdagingen om dit te implementeren. Hoe transformeert u een gecompileerde .NET-methode in een lijst met instructies en hoe formatteert u de instructies op een manier dat ze door het externe systeem kunnen worden gebruikt?
Zonder expressiebomen kon je alleen het eerste probleem met MSIL oplossen. (MSIL is de assembler-achtige code gemaakt door de .NET-compiler.) MSIL parseren is mogelijk , maar het is niet eenvoudig. Zelfs als je het goed parseert, kan het moeilijk zijn om te bepalen wat de oorspronkelijke bedoeling van de programmeur was met een bepaalde routine.
Uitdrukkingsbomen redden de dag
Expressiebomen pakken deze exacte problemen aan. Ze vertegenwoordigen programma-instructies, een boomgegevensstructuur waarbij elk knooppunt één instructie vertegenwoordigt en verwijzingen bevat naar alle informatie die u nodig hebt om die instructie uit te voeren. EenMethodCallExpression
verwijst bijvoorbeeld naar 1) de MethodInfo
het gaat oproepen, 2) een lijst met Expression
s die het aan die methode doorgeeft, 3) bijvoorbeeld methoden, de Expression
waarnaar u de methode oproept. U kunt "door de boom lopen" en de instructies toepassen op uw externe zoekopdracht. Uitdrukkingsbomen maken
De eenvoudigste manier om een expressieboom te maken is met een lambda-expressie. Deze uitdrukkingen zien er bijna hetzelfde uit als normale C # -methoden. Het is belangrijk om te beseffen dat dit compiler-magie is . Wanneer u voor het eerst een lambda-expressie maakt, controleert de compiler waaraan u deze toewijst. Als het eenDelegate
type is (inclusief Action
of Func
), converteert de compiler de lambda-expressie in een delegate. Als het een LambdaExpression
(of een Expression<Action<T>>
of Expression<Func<T>>
die sterk getypeerde LambdaExpression
's zijn), transformeert de compiler het in een LambdaExpression
. Dit is waar de magie van start gaat. Achter de schermen gebruikt de compiler de expression tree API om uw lambda-expressie om te zetten in een LambdaExpression
. Lambda-expressies kunnen niet elk type expressieboom maken. In die gevallen kunt u de Expressions API handmatig gebruiken om de gewenste structuur te maken. In het voorbeeld van de API voor uitdrukkingen begrijpen maken we de uitdrukking CalculateTotalSalesTax
met behulp van de API.
OPMERKING: de namen worden hier een beetje verwarrend. Een lambda-expressie (twee woorden, kleine letters) verwijst naar het codeblok met een =>
-indicator. Het vertegenwoordigt een anonieme methode in C # en wordt omgezet in een Delegate
of een Expression
. Een LambdaExpression
(één woord, PascalCase) verwijst naar het knooppunttype in de Expression API dat een methode vertegenwoordigt die u kunt uitvoeren.
Expressiebomen en LINQ
Een van de meest voorkomende toepassingen van expressiebomen is met LINQ en databasequery's. LINQ koppelt een expressieboom met een query-provider om uw instructies toe te passen op de externe doelquery. De queryprovider LINQ naar Entity Framework transformeert bijvoorbeeld een expressiestructuur in SQL die rechtstreeks tegen de database wordt uitgevoerd.
Als je alle stukjes samenvoegt, zie je de echte kracht achter LINQ.
- Schrijf een zoekopdracht met een lambda-expressie:
products.Where(x => x.Cost > 5)
- De compiler transformeert die uitdrukking in een uitdrukkingsboom met de instructies "controleer of de eigenschap Cost van de parameter groter is dan vijf".
- De queryprovider ontleedt de expressiestructuur en produceert een geldige SQL-query
SELECT * FROM products WHERE Cost > 5
- De ORM projecteert alle resultaten in POCO's en u krijgt een lijst met objecten terug
Notes
- Uitdrukkingsbomen zijn onveranderlijk. Als u een expressieboom wilt wijzigen, moet u een nieuwe maken, de bestaande naar de nieuwe kopiëren (om een expressieboom te doorlopen kunt u de
ExpressionVisitor
) en de gewenste wijzigingen aanbrengen.
Expressiebomen maken met behulp van de 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 });
Het samenstellen van expressiebomen
// 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));
Bomen met expressie ontleden
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
Maak expressiebomen met een lambda-expressie
Hierna volgt de meest basale expressieboom die is gemaakt door lambda.
Expression<Func<int, bool>> lambda = num => num == 42;
Om expressiebomen met de hand te maken, moet men de klasse Expression
gebruiken.
Bovenstaande uitdrukking zou gelijk zijn aan:
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);
Inzicht in de API voor uitdrukkingen
We gaan de expressieboom-API gebruiken om een CalculateSalesTax
boom te maken. In gewoon Engels, hier is een samenvatting van de stappen die het neemt om de boom te maken.
- Controleer of het product belastbaar is
- Als dit het geval is, vermenigvuldigt u het regeltotaal met het toepasselijke belastingtarief en geeft u dat bedrag terug
- Anders terug 0
//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);
Expression Tree Basic
Expressiebomen vertegenwoordigen code in een boomachtige gegevensstructuur, waarbij elk knooppunt een expressie is
Expression Trees maakt dynamische aanpassing van uitvoerbare code, het uitvoeren van LINQ-query's in verschillende databases en het maken van dynamische query's mogelijk. U kunt code compileren en uitvoeren die wordt weergegeven door expressiebomen.
Deze worden ook gebruikt in de dynamische taal runtime (DLR) om interoperabiliteit te bieden tussen dynamische talen en het .NET Framework en om compileerschrijvers in staat te stellen expressiebomen uit te stoten in plaats van Microsoft intermediaire taal (MSIL).
Expressiebomen kunnen worden gemaakt via
- Anonieme lambda-uitdrukking,
- Handmatig met de naamruimte System.Linq.Expressions.
Uitdrukkingsbomen van Lambda-uitdrukkingen
Wanneer een lambda-expressie wordt toegewezen aan de variabele Expression type, zendt de compiler code uit om een expression tree op te bouwen die de lambda-expressie vertegenwoordigt.
De volgende codevoorbeelden laten zien hoe de C # compiler een expressiestructuur moet maken die de lambda-expressie num => num <5 vertegenwoordigt.
Expression<Func<int, bool>> lambda = num => num < 5;
Expressiebomen met behulp van de API
Expression Trees ook gemaakt met behulp van de Expression Class. Deze klasse bevat statische fabrieksmethoden die expressiestructuurknooppunten van specifieke typen maken.
Hieronder staan enkele soorten boomknooppunten.
- ParameterExpression
- MethodCallExpression
Het volgende codevoorbeeld laat zien hoe u een expressieboom kunt maken die de lambda-expressie num => num <5 vertegenwoordigt met behulp van de API.
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 });
De structuur van een expressie onderzoeken met bezoeker
Definieer een nieuwe bezoekersklasse door een aantal methoden van ExpressionVisitor te overschrijven:
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);
}
}
Visit
om deze bezoeker op een bestaande uitdrukking te gebruiken:
Expression<Func<int,bool>> isBig = a => a > 1000000;
var visitor = new PrintingVisitor();
visitor.Visit(isBig);