Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit artikel leert u hoe u elk knooppunt in een expressiestructuur bezoekt terwijl u een gewijzigde kopie van die expressiestructuur bouwt. U vertaalt expressiestructuren om inzicht te hebben in de algoritmen, zodat deze in een andere omgeving kunnen worden vertaald. U wijzigt het algoritme dat is gemaakt. U kunt logboekregistratie toevoegen, methode-aanroepen onderscheppen en ze volgen, of voor andere doeleinden.
De code die u bouwt om een expressiestructuur te vertalen, is een uitbreiding van wat u al hebt gezien om alle knooppunten in een boomstructuur te bezoeken. Wanneer u een expressiestructuur vertaalt, gaat u naar alle knooppunten en bouwt u tijdens het bezoeken de nieuwe structuur. De nieuwe structuur kan verwijzingen bevatten naar de oorspronkelijke knooppunten of nieuwe knooppunten die u in de structuur hebt geplaatst.
We gaan een expressiestructuur bezoeken en een nieuwe structuur maken met enkele vervangende knooppunten. In dit voorbeeld gaan we elke constante vervangen door een constante die 10 keer groter is. Anders laat u de expressieboom intact. In plaats van de waarde van de constante te lezen en te vervangen door een nieuwe constante, maakt u deze vervanging door het constante knooppunt te vervangen door een nieuw knooppunt dat de vermenigvuldiging uitvoert.
Zodra u een constant knooppunt hebt gevonden, maakt u een nieuw vermenigvuldigingsknooppunt waarvan de onderliggende elementen de oorspronkelijke constante zijn en de constante 10
:
private static Expression ReplaceNodes(Expression original)
{
if (original.NodeType == ExpressionType.Constant)
{
return Expression.Multiply(original, Expression.Constant(10));
}
else if (original.NodeType == ExpressionType.Add)
{
var binaryExpression = (BinaryExpression)original;
return Expression.Add(
ReplaceNodes(binaryExpression.Left),
ReplaceNodes(binaryExpression.Right));
}
return original;
}
Maak een nieuwe boomstructuur door het oorspronkelijke knooppunt te vervangen door het vervangende knooppunt. U controleert de wijzigingen door de vervangen structuur te compileren en uit te voeren.
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);
var func = (Func<int>)executableFunc.Compile();
var answer = func();
Console.WriteLine(answer);
Het bouwen van een nieuwe boomstructuur is een combinatie van het bezoeken van de knooppunten in de bestaande structuur en het maken van nieuwe knooppunten en het invoegen ervan in de structuur. In het vorige voorbeeld ziet u het belang van expressiestructuren die onveranderbaar zijn. U ziet dat de nieuwe structuur die in de voorgaande code is gemaakt, een combinatie van nieuw gemaakte knooppunten en knooppunten uit de bestaande structuur bevat. Knooppunten kunnen in beide structuren worden gebruikt omdat de knooppunten in de bestaande structuur niet kunnen worden gewijzigd. Het hergebruik van knooppunten resulteert in aanzienlijke geheugenefficiëntie. Dezelfde knooppunten kunnen door een hele boom heen, of in meerdere expressiebomen worden gebruikt. Omdat knooppunten niet kunnen worden gewijzigd, kan hetzelfde knooppunt opnieuw worden gebruikt wanneer dit nodig is.
Doorkruisen en een optellen uitvoeren
Laten we de nieuwe boom controleren door een tweede bezoeker te bouwen die de boom van optellingsknooppunten doorloopt en het resultaat berekent. Breng een paar wijzigingen aan aan de bezoeker die u tot nu toe hebt gezien. In deze nieuwe versie retourneert de bezoeker de gedeeltelijke som van de optelbewerking tot dit punt. Voor een constante expressie is dit gewoon de waarde van de constante expressie. Voor een optelsom is het resultaat de som van de linker- en rechteroperanden, nadat deze bomen zijn doorkruist.
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var three = Expression.Constant(3, typeof(int));
var four = Expression.Constant(4, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2);
// Declare the delegate, so you can call it
// from itself recursively:
Func<Expression, int> aggregate = null!;
// Aggregate, return constants, or the sum of the left and right operand.
// Major simplification: Assume every binary expression is an addition.
aggregate = (exp) =>
exp.NodeType == ExpressionType.Constant ?
(int)((ConstantExpression)exp).Value :
aggregate(((BinaryExpression)exp).Left) + aggregate(((BinaryExpression)exp).Right);
var theSum = aggregate(sum);
Console.WriteLine(theSum);
Er is hier best veel code, maar de concepten zijn begrijpelijk. Deze code bezoekt kinderen in een uitgebreide eerste zoekopdracht. Wanneer het een constant knooppunt tegenkomt, retourneert de bezoeker de waarde van de constante. Nadat de bezoeker beide kinderen heeft bezocht, hebben die kinderen de som voor die subboom berekend. Het optelknooppunt kan nu zijn som berekenen. Zodra alle knooppunten in de expressiestructuur zijn bezocht, is de som berekend. U kunt de uitvoering traceren door het voorbeeld uit te voeren in het foutopsporingsprogramma en de uitvoering te traceren.
Laten we het makkelijker maken om te volgen hoe de knooppunten worden geanalyseerd en hoe de som wordt berekend door de boom te doorlopen. Hier is een bijgewerkte versie van de Aggregaatmethode die nogal wat traceerinformatie bevat.
private static int Aggregate(Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
{
var constantExp = (ConstantExpression)exp;
Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
if (constantExp.Value is int value)
{
return value;
}
else
{
return 0;
}
}
else if (exp.NodeType == ExpressionType.Add)
{
var addExp = (BinaryExpression)exp;
Console.Error.WriteLine("Found Addition Expression");
Console.Error.WriteLine("Computing Left node");
var leftOperand = Aggregate(addExp.Left);
Console.Error.WriteLine($"Left is: {leftOperand}");
Console.Error.WriteLine("Computing Right node");
var rightOperand = Aggregate(addExp.Right);
Console.Error.WriteLine($"Right is: {rightOperand}");
var sum = leftOperand + rightOperand;
Console.Error.WriteLine($"Computed sum: {sum}");
return sum;
}
else throw new NotSupportedException("Haven't written this yet");
}
Als u deze uitvoert op de sum
expressie, wordt de volgende uitvoer weergegeven:
10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10
Traceer de uitvoer en volg deze in de voorgaande code. U moet kunnen uitzoeken hoe de code elk knooppunt bezoekt en de som berekent terwijl deze door de structuur gaat en de som vindt.
Laten we nu eens kijken naar een andere uitvoering, met de expressie die wordt gegeven door sum1
:
Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));
Hier is het resultaat van het bekijken van deze uitdrukking.
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 2
Left is: 2
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10
Hoewel het laatste antwoord hetzelfde is, is de boomtraversatie anders. De knooppunten worden in een andere volgorde doorlopen, omdat de boom is opgebouwd met verschillende bewerkingen die als eerste plaatsvinden.
Een gewijzigde kopie maken
Maak een nieuw consoletoepassingsproject . Voeg een using
instructie toe aan het bestand voor de System.Linq.Expressions
naamruimte. Voeg de AndAlsoModifier
klasse toe aan uw project.
public class AndAlsoModifier : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.AndAlso)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
// Make this binary expression an OrElse operation instead of an AndAlso operation.
return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);
}
return base.VisitBinary(b);
}
}
Deze klasse neemt de ExpressionVisitor klasse over en is gespecialiseerd in het wijzigen van expressies die voorwaardelijke AND
bewerkingen vertegenwoordigen. Deze bewerkingen worden gewijzigd van een voorwaardelijk AND
in een voorwaardelijk OR
. De klasse overschrijft de VisitBinary methode van het basistype, omdat voorwaardelijke AND
expressies worden weergegeven als binaire expressies. Als in de VisitBinary
methode de expressie die aan de expressie wordt doorgegeven een voorwaardelijke AND
bewerking vertegenwoordigt, wordt met de code een nieuwe expressie gemaakt die de voorwaardelijke OR
operator bevat in plaats van de voorwaardelijke AND
operator. Als de expressie waaraan wordt doorgegeven VisitBinary
geen voorwaardelijke AND
bewerking vertegenwoordigt, wordt de methode uitgesteld tot de implementatie van de basisklasse. De basisklassemethoden maken knooppunten die lijken op de expressiestructuren die worden doorgegeven, maar de knooppunten hebben hun substructuren vervangen door de expressiestructuren die recursief door de bezoeker worden geproduceerd.
Voeg een using
instructie toe aan het bestand voor de System.Linq.Expressions
naamruimte. Voeg code toe aan de Main
methode in het Program.cs-bestand om een expressiestructuur te maken en door te geven aan de methode waarmee deze wordt gewijzigd.
Expression<Func<string, bool>> expr = name => name.Length > 10 && name.StartsWith("G");
Console.WriteLine(expr);
AndAlsoModifier treeModifier = new AndAlsoModifier();
Expression modifiedExpr = treeModifier.Modify((Expression)expr);
Console.WriteLine(modifiedExpr);
/* This code produces the following output:
name => ((name.Length > 10) && name.StartsWith("G"))
name => ((name.Length > 10) || name.StartsWith("G"))
*/
Met de code wordt een expressie gemaakt die een voorwaardelijke AND
bewerking bevat. Vervolgens wordt een exemplaar van de AndAlsoModifier
klasse gemaakt en wordt de expressie doorgegeven aan de Modify
methode van deze klasse. Zowel de oorspronkelijke als de gewijzigde expressiestructuren worden uitgevoerd om de wijziging weer te geven. Compileer de toepassing en voer deze uit.
Meer informatie
In dit voorbeeld ziet u een kleine subset van de code die u zou bouwen om de algoritmen te doorlopen en te interpreteren die worden vertegenwoordigd door een expressiestructuur. Lees deze reeks door Matt Warren voor informatie over het bouwen van een bibliotheek voor algemeen gebruik die expressiestructuren vertaalt in een andere taal. Het gaat uitgebreid in op het vertalen van een code die u in een expressiestructuur kunt vinden.
U hebt nu de ware kracht van expressiebomen gezien. U bekijkt een set code, breng eventuele wijzigingen aan die code aan en voer de gewijzigde versie uit. Omdat de expressiestructuren onveranderbaar zijn, maakt u nieuwe bomen met behulp van de onderdelen van bestaande bomen. Het hergebruik van knooppunten minimaliseert de hoeveelheid geheugen die nodig is om gewijzigde expressiestructuren te maken.