Kifejezésfák – kódokat meghatározó adatok

A kifejezésfa egy kódokat definiáló adatstruktúra. A kifejezésfák ugyanazokon a struktúrákon alapulnak, amelyeket a fordító a kód elemzéséhez és a lefordított kimenet létrehozásához használ. A cikk elolvasása során meglehetősen hasonlóságot tapasztal a Kifejezésfák és a Roslyn API-kban az Elemzők és CodeFixek létrehozásához használt típusok között. (Az elemzők és a CodeFixek olyan NuGet-csomagok, amelyek statikus elemzést végeznek a kódon, és lehetséges javításokat javasolnak egy fejlesztő számára.) A fogalmak hasonlóak, és a végeredmény egy olyan adatstruktúra, amely lehetővé teszi a forráskód értelmes vizsgálatát. A Kifejezésfák azonban a Roslyn API-któl eltérő osztályokon és API-kon alapulnak. Íme egy kódsor:

var sum = 1 + 2;

Ha az előző kódot kifejezésfaként elemzi, a fa több csomópontot tartalmaz. A legkülső csomópont egy változódeklarációs utasítás hozzárendeléssel (var sum = 1 + 2;) Ez a legkülső csomópont több gyermekcsomópontot tartalmaz: egy változódeklarációt, egy hozzárendelési operátort és egy, az egyenlőségjel jobb oldalát jelképező kifejezést. Ez a kifejezés további részre van osztva az összeadási műveletet képviselő kifejezésekre, valamint az összeadás bal és jobb operandusára.

Vizsgáljuk meg egy kicsit jobban az egyenlőségjel jobb oldalát alkotó kifejezéseket. A kifejezés bináris 1 + 2kifejezés. Pontosabban bináris összeadási kifejezés. A bináris összeadási kifejezés két gyermekből áll, amelyek az összeadási kifejezés bal és jobb csomópontjait jelölik. Itt mindkét csomópont állandó kifejezés: A bal operandus az érték 1, a jobb operandus pedig az érték 2.

Vizuálisan a teljes utasítás egy fa: A gyökércsomóponton kezdhet, és a fa minden csomópontjába utazva megtekintheti az utasítást alkotó kódot:

  • Változódeklarációs utasítás hozzárendeléssel (var sum = 1 + 2;)
    • Implicit változótípus deklarációja (var sum)
      • Implicit var kulcsszó (var)
      • Változónév-deklarálás (sum)
    • Hozzárendelés-operátor (=)
    • Bináris összeadási kifejezés (1 + 2)
      • Bal műveleti elem (1)
      • Összeadás operátor (+)
      • Jobb operandus (2)

Az előző fa bonyolultnak tűnhet, de nagyon erős. Ugyanezt a folyamatot követve sokkal bonyolultabb kifejezéseket bonthat le. Fontolja meg ezt a kifejezést:

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

Az előző kifejezés egy hozzárendeléssel rendelkező változódeklaráció is. Ebben az esetben a feladat jobb oldala egy sokkal bonyolultabb fa. Ezt a kifejezést nem bontja fel, de gondolja át, hogy mik lehetnek a különböző csomópontok. Vannak metódushívások, amelyek az aktuális objektumot használják fogadóként, amely explicit this fogadóval rendelkezik, és amely nem. Vannak metódushívások más fogadóobjektumokkal, különböző típusú állandó argumentumok vannak. És végül van egy bináris összeadási operátor. Attól függően, hogy a SecretSauceFunction() vagy a MoreSecretSauce() milyen visszatérési típust ad meg, a bináris összeadási operátor lehet, hogy egy felülírt összeadási operátor metódushívása lesz, amely egy osztályhoz definiált bináris összeadási operátor statikus metódushívásává oldódik.

Az észlelt összetettség ellenére az előző kifejezés olyan könnyen navigálható faszerkezetet hoz létre, mint az első minta. A gyermekcsomópontokon folyamatosan áthaladva megkeresheti a levélcsomópontokat a kifejezésben. A szülőcsomópontok a gyermekeikre mutató hivatkozásokkal rendelkeznek, és minden csomópont rendelkezik egy olyan tulajdonságkal, amely leírja, hogy milyen típusú csomópont.

A kifejezésfa szerkezete nagyon konzisztens. Miután megismerte az alapokat, még a legösszetettebb kódot is megismerheti, amikor kifejezésfaként van ábrázolva. Az adatstruktúra eleganciáját mutatja be, hogy a C#-fordító hogyan elemzi a legösszetettebb C#-programokat, és hogyan hoz létre megfelelő kimenetet ebből a bonyolult forráskódból.

Miután megismerte a kifejezésfák szerkezetét, azt tapasztalhatja, hogy a megszerzett tudás gyorsan lehetővé teszi, hogy sokkal fejlettebb forgatókönyvekkel dolgozzon. Hihetetlen ereje van az kifejezési fáknak.

A más környezetekben végrehajtandó algoritmusok fordítása mellett a kifejezésfák megkönnyítik az olyan algoritmusok írását, amelyek a végrehajtás előtt ellenőrzik a kódot. A kód végrehajtása előtt megír egy metódust, amelynek argumentumai kifejezések, majd megvizsgálja ezeket a kifejezéseket. A kifejezésfa a kód teljes ábrázolása: minden alexpresszió értékei láthatók. A metódusok és a tulajdonságnevek láthatók. Minden állandó kifejezés értékét láthatja. A kifejezésfát végrehajtható delegálttá alakíthatja, és végrehajthatja a kódot.

A Kifejezésfák API-kkal szinte bármilyen érvényes kódszerkezetet képviselő fák hozhatók létre. Ha azonban a lehető legegyszerűbben szeretné tartani a dolgokat, egyes C#-kifejezések nem hozhatók létre egy kifejezésfában. Ilyen például az aszinkron kifejezés (a async és await kulcsszavak használatával). Ha az igényei aszinkron algoritmusokat igényelnek, akkor közvetlenül kell manipulálnia az Task objektumokat, nem pedig a fordítótámogatásra kell támaszkodnia. A másik a hurkok létrehozása. Általában ezek a hurkok a for, foreach, while vagy do hurkok használatával jönnek létre. A sorozat későbbi részében látható, hogy a kifejezésfák API-jai egyetlen ciklusos kifejezést támogatnak, break valamint continue a ciklus ismétlését vezérlő kifejezéseket.

Az egyetlen dolog, amit nem tehet, ha módosít egy kifejezésfát. A kifejezésfák nem módosítható adatstruktúrák. Ha módosítani szeretne egy kifejezésfát, létre kell hoznia egy új fát, amely az eredeti példány másolata, de a kívánt módosításokkal együtt.