Megosztás a következőn keresztül:


Mintaegyeztetési változások a C# 9.0-hoz

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

A C# 9.0 mintaegyeztetésének néhány olyan fejlesztését fontolgatjuk, amelyek természetes szinergiával rendelkeznek, és jól működnek számos gyakori programozási probléma megoldásához:

Zárójeles minták

A zárójeles minták lehetővé teszik, hogy a programozó zárójeleket helyezzen bármilyen minta köré. Ez nem annyira hasznos a C# 8.0 meglévő mintáinál, azonban az új minta-kombinátorok olyan elsőbbséget élveznek, amelyet a programozó felül szeretne bírálni.

primary_pattern
    : parenthesized_pattern
    | // all of the existing forms
    ;
parenthesized_pattern
    : '(' pattern ')'
    ;

Típusminták

Mintaként engedélyezünk egy típust:

primary_pattern
    : type-pattern
    | // all of the existing forms
    ;
type_pattern
    : type
    ;

Ez visszamenőleg módosítja a meglévő is-type-expression úgy, hogy az egy is-pattern-expression legyen, amelyben a minta egy típus-minta, annak ellenére, hogy a fordító által létrehozott szintaxisfát nem módosítanánk.

Az egyik apró megvalósítási probléma az, hogy ez a nyelvtan nem egyértelmű. Az olyan sztringek, mint a a.b, minősített névként (típuskörnyezetben) vagy pontozott kifejezésként (kifejezéskörnyezetben) elemezhetők. A fordító már képes a minősített nevet úgy kezelni, mint a pontozott kifejezést annak érdekében, hogy kezelni tudjon valamit, például e is Color.Red-hoz hasonlót. A fordító szemantikai elemzése tovább bővülne, hogy képes legyen egy (szintaktikai) állandó mintát (például pontozott kifejezést) típusként kötni, hogy a szerkezet támogatása érdekében azt kötött típusmintaként kezelje.

A módosítás után képes lesz írni

void M(object o1, object o2)
{
    var t = (o1, o2);
    if (t is (int, string)) {} // test if o1 is an int and o2 is a string
    switch (o1) {
        case int: break; // test if o1 is an int
        case System.String: break; // test if o1 is a string
    }
}

Relációs minták

A relációs minták lehetővé teszik a programozó számára, hogy kifejezze, hogy egy bemeneti értéknek meg kell felelnie egy relációs kényszernek egy állandó értékhez képest:

    public static LifeStage LifeStageAtAge(int age) => age switch
    {
        < 0 =>  LifeStage.Prenatal,
        < 2 =>  LifeStage.Infant,
        < 4 =>  LifeStage.Toddler,
        < 6 =>  LifeStage.EarlyChild,
        < 12 => LifeStage.MiddleChild,
        < 20 => LifeStage.Adolescent,
        < 40 => LifeStage.EarlyAdult,
        < 65 => LifeStage.MiddleAdult,
        _ =>    LifeStage.LateAdult,
    };

A relációs minták támogatják a relációs operátorokat, <, <=, >és >= minden olyan beépített típuson, amely támogatja az ilyen bináris relációs operátorokat két azonos típusú operandussal egy kifejezésben. Kifejezetten támogatjuk ezeket a relációs mintákat: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, nintés nuint.

primary_pattern
    : relational_pattern
    ;
relational_pattern
    : '<' relational_expression
    | '<=' relational_expression
    | '>' relational_expression
    | '>=' relational_expression
    ;

A kifejezésnek állandó értéket kell eredményeznie. Hiba, ha az állandó érték double.NaN vagy float.NaN. Hiba, ha a kifejezés null állandó.

Ha a bemenet egy olyan típusú, amelyhez egy alkalmazható beépített bináris relációs operátor van meghatározva, ahol a bemenet a bal operandus és az adott állandó a jobb operandus, akkor az operátor kiértékelését a relációs minta jelentéseként kell értelmezni. Ellenkező esetben a bemenetet explicit null értékű vagy nemboxolásos átalakítással alakítjuk át a kifejezés típusára. Fordítási időpontban hiba lép fel, ha nincs ilyen átalakítás. A minta nem tekinthető egyezőnek, ha az átalakítás sikertelen. Ha az átalakítás sikeres, akkor a mintaegyeztetési művelet eredménye annak a kifejezésnek az kiértékelése, e OP v ahol e a konvertált bemenet, OP a relációs operátor, v pedig az állandó kifejezés.

Minta-kombinátorok

A mintakombinátorok lehetővé teszik két különböző mintának az egyidejű egyezését and használatával (ez a andismételt használatával bármilyen számú mintára kiterjeszthető), vagy az egyik a két különböző minta közül or használatával (u.a.), vagy egy minta not.

A kombinátor gyakori használata az idioma lesz

if (e is not null) ...

Olvashatóbb, mint a jelenlegi kifejezés e is object, ez a minta egyértelműen kifejezi, hogy nem nullérték ellenőrzéséről van szó.

A and és or kombinátorok hasznosak lesznek az értéktartományok teszteléséhez

bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';

Ez a példa azt szemlélteti, hogy a and magasabb elemzési prioritással (azaz szorosabb kötéssel) rendelkezik, mint or. A programozó a zárójeles mintával az elsőbbséget explicitvá teheti:

bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Mint minden minta, ezek a kombinátorok is használhatók minden olyan környezetben, amelyben minta várható, beleértve a beágyazott mintákat, a mintakifejezésként, a kapcsolókifejezésként, valamint a kapcsolóutasítás eset címkéjének mintáját.

pattern
    : disjunctive_pattern
    ;
disjunctive_pattern
    : disjunctive_pattern 'or' conjunctive_pattern
    | conjunctive_pattern
    ;
conjunctive_pattern
    : conjunctive_pattern 'and' negated_pattern
    | negated_pattern
    ;
negated_pattern
    : 'not' negated_pattern
    | primary_pattern
    ;
primary_pattern
    : // all of the patterns forms previously defined
    ;

Ugrás a 6.2.5-ös nyelvtani kétértelműségekre

A típusmintabevezetése miatt előfordulhat, hogy egy általános típus megjelenhet a jelölő =>előtt. Ezért a => a §6.2.5 Nyelvtani kétértelműségek listában felsorolt tokenek készletéhez kerül hozzáadásra, hogy lehetővé tegye a típusargumentumlistát kezdő < egyértelműsítését. Lásd még: https://github.com/dotnet/roslyn/issues/47614.

Javasolt módosításokkal kapcsolatos problémák megnyitása

Relációs operátorok szintaxisa

and, orés not valamilyen környezetfüggő kulcsszavak? Ha igen, akkor van-e kompatibilitástörő változás (például a deklarációs mintatervezőként való használatukhoz képest).

Szemantika (pl. típus) a relációs operátorok esetében

Várhatóan támogatni fogjuk az összes olyan primitív típust, amely relációs operátor használatával hasonlítható össze egy kifejezésben. A jelentés egyszerű esetekben egyértelmű

bool IsValidPercentage(int x) => x is >= 0 and <= 100;

De ha a bemenet nem ilyen primitív típus, milyen típusra próbáljuk átalakítani?

bool IsValidPercentage(object x) => x is >= 0 and <= 100;

Azt javasoltuk, hogy ha a bemeneti típus már összehasonlítható primitív, akkor ez az összehasonlítás típusa. Ha azonban a bemenet nem összehasonlítható primitív, akkor a relációt úgy kezeljük, mint ami implicit típustesztet tartalmaz a jobb oldalon lévő állandó típusára. Ha a programozó egynél több bemeneti típust kíván támogatni, ezt explicit módon kell elvégezni:

bool IsValidPercentage(object x) => x is
    >= 0 and <= 100 or    // integer tests
    >= 0F and <= 100F or  // float tests
    >= 0D and <= 100D;    // double tests

Eredmény: A relációs tartalmaz egy implicit típustesztet a relációs konstans típusára a relációs jobb oldalán.

A típusinformációk balról jobbra folynak a and-n

Azt javasolták, hogy amikor and kombinátort ír, a bal oldalon megszerzett felső szintű típussal kapcsolatos információk esetleg jobbra áramolhatnak. Például

bool isSmallByte(object o) => o is byte and < 100;

Itt a második mintára bemeneti típust a típusszűkítési követelmények szűkítik a bal oldalán. A típusszűkítés szemantikáját az alábbiak szerint határoznánk meg az összes mintához. A minta szűkített típusa a következőképpen van definiálva:

  1. Ha P típusminta, akkor a szűkített típus a típusminta típusának típusa.
  2. Ha P deklarációs minta, a szűkített típus a deklarációs minta típusának típusa.
  3. Ha P egy rekurzív minta, amely explicit típust ad, a szűkített típus ez a típus.
  4. Ha aszabályaival egyezni, akkor a szűkített típus a típus.
  5. Ha P egy olyan állandó minta, ahol az állandó nem null értékű, és ahol a kifejezés nem konvertálható állandó kifejezésként típusú bemenetre, akkor a szűkített típus az állandó típusa.
  6. Ha P olyan relációs minta, amelyben az állandó kifejezés nem konstanskifejezés-konverziósbemeneti típusra, akkor a szűkített típus az állandó típusa.
  7. Ha Por minta, akkor a szűkített típus az al-minták szűkített típusának közös típusa, ha létezik ilyen közös típus. Ebből a célból a közös típusú algoritmus csak az azonosság-, a boxing- és az implicit referencia konverziókat veszi figyelembe, és figyelembe veszi a or minták sorozatának minden részmintáit (figyelmen kívül hagyva a zárójeles mintákat).
  8. Ha P egy and minta, akkor a szűkített típus a szűkített típus a megfelelő minta szerint. Ezenkívül a bal minta beszűkített típusa a jobb minta bemeneti típusa .
  9. Ellenkező esetben a PPbemeneti típusa.

Eredmény: A fenti szűkítő szemantikát implementáltuk.

Változódefiníciók és határozott hozzárendelés

A or és not minták hozzáadása érdekes új problémákat okoz a mintaváltozók és a határozott hozzárendelés körül. Mivel a változók általában legfeljebb egyszer deklarálhatók, úgy tűnik, hogy a or minta egyik oldalán deklarált mintaváltozók nem lesznek egyértelműen hozzárendelve, amikor a minta egyezik. Hasonlóképpen, egy not mintában deklarált változót nem kell feltétlenül hozzárendelni, amikor a minta egyezik. Ennek legegyszerűbb módja, ha megtiltja a mintaváltozók deklarálását ezekben a kontextusokban. Ez azonban túl korlátozó lehet. Más megközelítéseket is figyelembe kell venni.

Az egyik olyan forgatókönyv, amelyet érdemes megfontolni,

if (e is not int i) return;
M(i); // is i definitely assigned here?

Ez ma nem működik, mert egy is-pattern-expressionesetében a mintaváltozókat csak akkor tekintik egyértelműen hozzárendeltnek , ha az is-pattern-expression igaz ("határozottan hozzárendelve, ha igaz").

Ennek támogatása egyszerűbb lenne (a programozó szemszögéből), mint egy negated-condition if állítás támogatása. Még ha ilyen támogatást is adunk hozzá, a programozók csodálkoznának, hogy a fenti kódrészlet miért nem működik. Másrészt, ugyanez a forgatókönyv egy switch esetében kevésbé értelmes, mivel nincs megfelelő pont a programban, ahol a egyértelműen hozzá lett volna rendelve, amikor a hamis értelmes lett volna. Engedélyeznénk ezt egy is-pattern-expression-ben, de nem más olyan környezetekben, ahol a minták engedélyezettek? Ez szabálytalannak tűnik.

Ehhez kapcsolódik a határozott hozzárendelés problémája egy diszjunktív mintázatban.

if (e is 0 or int i)
{
    M(i); // is i definitely assigned here?
}

Csak akkor várnánk, hogy a i biztosan hozzárendelésre kerüljön, ha a bemenet nem nulla. Mivel azonban nem tudjuk, hogy a bemenet nulla-e vagy sem a blokkon belül, i nincs egyértelműen hozzárendelve. Azonban, mi van akkor, ha lehetővé tesszük, hogy a i különböző, kölcsönösen kizáró mintákban legyen deklarálva?

if ((e1, e2) is (0, int i) or (int i, 0))
{
    M(i);
}

Itt a i változó egyértelműen hozzá van rendelve a blokkon belül, és a nulla elem megtalálásakor a tuple másik elemétől veszi az értéket.

Azt is javasolták, hogy tegyék lehetővé a változók (többszöri) definiálását minden esetblokk esetében.

    case (0, int x):
    case (int x, 0):
        Console.WriteLine(x);

A munka elvégzéséhez gondosan meg kell határoznunk, hogy hol engedélyezhetők ilyen több definíciók, és milyen feltételek mellett tekinthető egy ilyen változó egyértelműen hozzárendeltnek.

Ha úgy döntenénk, hogy későbbre halasztjuk az ilyen munkát (amit tanácsolok), akkor a C# 9-ben ezt mondhatnánk.

  • not vagy oralatt a mintaváltozók nem deklarálhatók.

Ezután lenne időnk olyan tapasztalatokat fejleszteni, amelyek betekintést nyújtanak a későbbi lazítás lehetséges értékébe.

Eredmény: A mintaváltozók nem deklarálhatók not vagy or minta alatt.

Diagnosztika, alárendelés és teljesség

Ezek az új mintaűrlapok számos új lehetőséget adnak a felismerhető programozói hibákra. El kell döntenünk, hogy milyen típusú hibákat diagnosztizálunk, és hogyan kell ezt megtenni. Íme néhány példa:

case >= 0 and <= 100D:

Ez az eset soha nem egyezhet meg (mivel a bemenet nem lehet int és doubleis). Már van egy hiba, amikor olyan esetet észlelünk, amely soha nem egyezik, de a megfogalmazása ("A kapcsoló esetet már egy korábbi eset kezelte" és "A mintát már kezelte a kapcsolókifejezés egy korábbi ága") új helyzetekben félrevezető lehet. Lehet, hogy módosítani kell a szövegezést, hogy csak azt mondjuk, hogy a minta soha nem egyezik a bemenettel.

case 1 and 2:

Hasonlóképpen, ez hiba lenne, mert egy érték nem lehet 1 és 2is.

case 1 or 2 or 3 or 1:

Ez az eset megfeleltethető, de a végén lévő or 1 nem ad értelmet a mintának. Azt javaslom, hogy törekedjünk arra, hogy hibát állítsunk elő, amikor egy összetett minta egy kötő- vagy disjunctja nem határoz meg egy mintaváltozót, vagy befolyásolja a megfeleltethető értékek készletét.

case < 2: break;
case 0 or 1 or 2 or 3 or 4 or 5: break;

Itt 0 or 1 or semmit sem ad hozzá a második esethez, mivel ezeket az értékeket az első eset kezelte volna. Ez is megérdemel egy hibát.

byte b = ...;
int x = b switch { <100 => 0, 100 => 1, 101 => 2, >101 => 3 };

Az ilyen kapcsolókifejezéseket teljes kell tekinteni (az összes lehetséges bemeneti értéket kezeli).

A C# 8.0-ban a byte típusú bemenettel rendelkező kapcsolókifejezések csak akkor tekinthetők kimerítőnek, ha olyan végleges kart tartalmaz, amelynek mintája mindennek megfelel (eldobott minta vagy var-minta). A C# 8-ban még az olyan kapcsolókifejezések sem tekinthetők teljesnek, amelyek minden különböző byte értékhez rendelkezik karnal. A relációs minták teljességének megfelelő kezeléséhez ezt az esetet is kezelni kell. Ez gyakorlatilag egy kompatibilitástörő változás lesz, de valószínűleg egy felhasználó sem fogja észrevenni.