Share via


Expressiestructuren - gegevens die code definiëren

Een expressiestructuur is een gegevensstructuur die code definieert. Expressiestructuren zijn gebaseerd op dezelfde structuren die een compiler gebruikt om code te analyseren en de gecompileerde uitvoer te genereren. Terwijl u dit artikel leest, ziet u nogal wat overeenkomsten tussen Expression Trees en de typen die in de Roslyn-API's worden gebruikt om Analyzers en CodeFixes te bouwen. (Analyzers en CodeFixes zijn NuGet-pakketten die statische analyses uitvoeren op code en mogelijke oplossingen voorstellen voor een ontwikkelaar.) De concepten zijn vergelijkbaar en het eindresultaat is een gegevensstructuur waarmee de broncode op een zinvolle manier kan worden onderzocht. Expressiestructuren zijn echter gebaseerd op een andere set klassen en API's dan de Roslyn-API's. Hier volgt een regel code:

var sum = 1 + 2;

Als u de voorgaande code analyseert als een expressiestructuur, bevat de structuur verschillende knooppunten. Het buitenste knooppunt is een instructie voor variabeledeclaratie met toewijzing (var sum = 1 + 2;) Dat buitenste knooppunt bevat verschillende onderliggende knooppunten: een variabeledeclaratie, een toewijzingsoperator en een expressie die de rechterkant van het gelijkteken vertegenwoordigt. Deze expressie is verder onderverdeeld in expressies die de optellingsbewerking vertegenwoordigen, en de linker- en rechteroperand van de optelling.

Laten we eens wat meer inzoomen op de expressies waaruit de rechterkant van het gelijkteken bestaat. De expressie is 1 + 2, een binaire expressie. Meer specifiek is het een binaire optellingsexpressie. Een binaire optellingsexpressie heeft twee onderliggende elementen, die de linker- en rechterknooppunten van de optellingsexpressie vertegenwoordigen. Hier zijn beide knooppunten constante expressies: de linkeroperand is de waarde 1en de rechteroperand is de waarde 2.

Visueel is de hele instructie een structuur: u kunt beginnen bij het hoofdknooppunt en naar elk knooppunt in de structuur gaan om de code te zien waaruit de instructie bestaat:

  • Instructie voor variabele declaratie met toewijzing (var sum = 1 + 2;)
    • Impliciete declaratie van variabeletypen (var sum)
      • Impliciet var-trefwoord (var)
      • Naamdeclaratie van variabele (sum)
    • Toewijzingsoperator (=)
    • Expressie voor binaire toevoeging (1 + 2)
      • Linkeroperand (1)
      • Operator voor optellen (+)
      • Rechteroperand (2)

De voorgaande boom ziet er ingewikkeld uit, maar het is erg krachtig. Na hetzelfde proces ontleden u veel gecompliceerdere expressies. Houd rekening met deze expressie:

var finalAnswer = this.SecretSauceFunction(
    currentState.createInterimResult(), currentState.createSecondValue(1, 2),
    decisionServer.considerFinalOptions("hello")) +
    MoreSecretSauce('A', DateTime.Now, true);

De voorgaande expressie is ook een variabeledeclaratie met een toewijzing. In dit geval is de rechterkant van de opdracht een veel gecompliceerdere boom. U gaat deze expressie niet ontleden, maar bedenk wat de verschillende knooppunten kunnen zijn. Er zijn methodeaanroepen met behulp van het huidige object als ontvanger, een met een expliciete this ontvanger, een die dat niet doet. Er zijn methode-aanroepen met andere ontvangerobjecten, er zijn constante argumenten van verschillende typen. En ten slotte is er een binaire optellingsoperator. Afhankelijk van het retourtype van SecretSauceFunction(), kan die binaire opteloperator een methodeaanroep zijn naar een overschreven opteloperator, waarbij deze wordt omgezet in een aanroep van een statische methode naar de binaire opteloperator die is gedefinieerd voor een klasse.

Ondanks deze waargenomen complexiteit creëert de voorgaande uitdrukking een boomstructuur die even gemakkelijk te navigeren is als het eerste voorbeeld. U blijft onderliggende knooppunten doorlopen om bladknooppunten in de expressie te vinden. Bovenliggende knooppunten hebben verwijzingen naar hun kinderen, en elk knooppunt heeft een eigenschap die beschrijft welk type knooppunt het is.

De structuur van een expressieboom is zeer uniform. Zodra u de basisbeginselen hebt geleerd, begrijpt u zelfs de meest complexe code wanneer deze wordt weergegeven als een expressiestructuur. De elegantie in de gegevensstructuur legt uit hoe de C#-compiler de meest complexe C#-programma's analyseert en de juiste uitvoer maakt van die gecompliceerde broncode.

Zodra u vertrouwd raakt met de structuur van expressiestructuren, zult u merken dat u snel kennis hebt opgedaan waarmee u met veel meer geavanceerde scenario's kunt werken. Er is ongelooflijke kracht in expressiebomen.

Naast het vertalen van algoritmen die moeten worden uitgevoerd in andere omgevingen, maken expressiestructuren het eenvoudiger om algoritmen te schrijven die code inspecteren voordat ze worden uitgevoerd. U schrijft een methode waarvan de argumenten expressies zijn en onderzoekt deze expressies voordat u de code uitvoert. De expressiestructuur is een volledige weergave van de code: u ziet waarden van een subexpressie. U ziet de namen van methoden en eigenschappen. U ziet de waarde van eventuele constante expressies. U converteert een expressiestructuur naar een uitvoerbare gemachtigde en voert de code uit.

Met de API's voor Expression Trees kunt u bomen maken die vrijwel elke geldige codeconstructie vertegenwoordigen. Als u het echter zo eenvoudig mogelijk wilt houden, kunnen sommige C#-idiomen niet worden gemaakt in een expressiestructuur. Een voorbeeld hiervan zijn asynchrone expressies (met behulp van de async en await trefwoorden). Als u asynchrone algoritmen nodig hebt, moet u de Task objecten rechtstreeks bewerken in plaats van te vertrouwen op de ondersteuning van de compiler. Een ander aspect is het maken van lussen. Normaal gesproken maakt u deze lussen met behulp vanfor, foreachwhile of do lussen. Zoals u verderop in deze reeks ziet, ondersteunen de API's voor expressiestructuren één lusexpressie, met break en continue expressies waarmee de lus wordt herhaald.

Het enige wat u niet kunt doen, is een expressiestructuur wijzigen. Expressiestructuren zijn onveranderbare gegevensstructuren. Als u een expressieboom wilt muteren (wijzigen), moet u een nieuwe boom maken die een kopie is van het origineel, maar met de gewenste wijzigingen.