Mintaegyeztetés áttekintése

A mintaegyeztetés egy olyan technika, amely során teszteli a kifejezéseket, hogy megállapítsa, rendelkezik-e bizonyos jellemzőkkel. A C#-mintaegyezés tömörebb szintaxist biztosít a kifejezések teszteléséhez és a kifejezések egyezése esetén végrehajtandó műveletekhez. A "is kifejezés" támogatja a mintaegyezést egy kifejezés teszteléséhez, és feltételesen deklarál egy új változót a kifejezés eredményéhez. A "switch kifejezés" lehetővé teszi a műveletek végrehajtását egy kifejezés első egyező mintája alapján. Ez a két kifejezés a minták gazdag szókészletét támogatja.

Ez a cikk áttekintést nyújt azokról a forgatókönyvekről, amelyekben mintaegyezést használhat. Ezek a technikák javíthatják a kód olvashatóságát és helyességét. Az összes alkalmazható minta teljes körű ismertetését a nyelvi referencia mintáiról szóló cikkben találja.

Null értékű ellenőrzések

A mintaegyeztetés egyik leggyakoribb forgatókönyve, hogy az értékek nem null. A null értékű értéktípusokat tesztelheti és konvertálhatja annak alapjául szolgáló típussá null , miközben az alábbi példát használja:

int? maybe = 12;

if (maybe is int number)
{
    Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
    Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}

Az előző kód egy deklarációs minta a változó típusának teszteléséhez és egy új változóhoz való hozzárendeléséhez. A nyelvi szabályok sokkal biztonságosabbá teszik ezt a technikát, mint sok más. A változó number csak a záradék valódi részében érhető el és rendelhető hozzá if . Ha máshol próbálja elérni, akár a else záradékban, akár a if blokk után, a fordító hibát ad ki. Másodszor, mivel nem az operátort használja, ez a == minta akkor működik, ha egy típus túlterheli az operátort == . Így ideális módszer a null referenciaértékek ellenőrzésére, a not minta hozzáadásával:

string? message = ReadMessageOrDefault();

if (message is not null)
{
    Console.WriteLine(message);
}

Az előző példa egy állandó mintát használt a változó nullösszehasonlításához. Ez not egy logikai minta , amely akkor egyezik, ha a negated minta nem egyezik.

Típustesztek

A mintaegyeztetés másik gyakori módja egy változó tesztelése, hogy kiderüljön, megfelel-e egy adott típusnak. Az alábbi kód például ellenőrzi, hogy egy változó nem null értékű-e, és implementálja-e az interfészt System.Collections.Generic.IList<T> . Ha igen, a ICollection<T>.Count lista tulajdonságát használja a középső index megkereséséhez. A deklarációs minta nem egyezik meg egy null értékkel, függetlenül a változó fordítási idejének típusától. Az alábbi kód a nullnem implementálható IListtípusok elleni védelem mellett véd.

public static T MidPoint<T>(IEnumerable<T> sequence)
{
    if (sequence is IList<T> list)
    {
        return list[list.Count / 2];
    }
    else if (sequence is null)
    {
        throw new ArgumentNullException(nameof(sequence), "Sequence can't be null.");
    }
    else
    {
        int halfLength = sequence.Count() / 2 - 1;
        if (halfLength < 0) halfLength = 0;
        return sequence.Skip(halfLength).First();
    }
}

Ugyanezek a tesztek alkalmazhatók egy switch kifejezésben egy változó több különböző típuson való teszteléséhez. Ezekkel az információkkal jobb algoritmusokat hozhat létre az adott futásidejű típus alapján.

Különálló értékek összehasonlítása

Tesztelhet egy változót is, hogy megkeressen egyezést adott értékeken. Az alábbi kód egy példát mutat be, amelyben egy enumerálásban deklarált összes lehetséges értéken tesztel egy értéket:

public State PerformOperation(Operation command) =>
   command switch
   {
       Operation.SystemTest => RunDiagnostics(),
       Operation.Start => StartSystem(),
       Operation.Stop => StopSystem(),
       Operation.Reset => ResetToReady(),
       _ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
   };

Az előző példa egy enumerálás értékén alapuló metódusküldést mutat be. Az utolsó _ eset egy elvetési minta , amely megfelel az összes értéknek. Kezeli azokat a hibafeltételeket, amelyekben az érték nem egyezik meg a megadott enum értékek egyikével. Ha kihagyja a kapcsolókart, a fordító figyelmezteti, hogy a mintakifejezés nem kezeli az összes lehetséges bemeneti értéket. Futásidőben a switch kifejezés kivételt okoz, ha a vizsgált objektum nem egyezik a kapcsolókarok egyikével sem. Számállandókat használhat számértékek helyett. Ezt a hasonló technikát a parancsokat képviselő állandó sztringértékekhez is használhatja:

public State PerformOperation(string command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

Az előző példa ugyanazt az algoritmust mutatja be, de sztringértékeket használ szám helyett. Ezt a forgatókönyvet akkor használná, ha az alkalmazás normál adatformátum helyett szöveges parancsokra válaszol. A C# 11-től kezdve állandó sztringértékek tesztelésére is használhat egy Span<char> vagy egy függvényt ReadOnlySpan<char>, ahogyan az alábbi példában látható:

public State PerformOperation(ReadOnlySpan<char> command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

Ezekben a példákban az elvetési minta biztosítja, hogy minden bemenetet kezeljen. A fordító segít abban, hogy minden lehetséges bemeneti értéket kezeljen.

Relációs minták

Relációs minták használatával tesztelheti, hogyan hasonlít össze egy érték az állandókkal. A következő kód például a víz állapotát adja vissza a Fahrenheit hőmérséklete alapján:

string WaterState(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        (> 32) and (< 212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid / gas transition",
    };

Az előző kód a kötőjeles andlogikai mintát is bemutatja annak ellenőrzéséhez, hogy mindkét relációs minta egyezik-e. A disjunctive or mintát is használhatja annak ellenőrzésére, hogy mindkét minta egyezik-e. A két relációs mintát zárójelek veszik körül, amelyeket bármilyen minta körül használhat az egyértelműség érdekében. Az utolsó két kapcsolókar kezeli az olvadási pont és a forráspont eseteit. E két kar nélkül a fordító figyelmezteti, hogy a logika nem fedi le az összes lehetséges bemenetet.

Az előző kód egy másik fontos funkciót is bemutat, amely a fordító mintául szolgáló kifejezéseket biztosít: A fordító figyelmezteti Önt, ha nem kezeli az összes bemeneti értéket. A fordító figyelmeztetést is ad, ha a kapcsolókar mintáját egy korábbi minta fedi le. Ez szabadságot biztosít a kifejezések újrabontására és átrendezésére. Ugyanez a kifejezés írásának másik módja a következő lehet:

string WaterState2(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        < 32 => "solid",
        32 => "solid/liquid transition",
        < 212 => "liquid",
        212 => "liquid / gas transition",
        _ => "gas",
};

Az előző példában, valamint az újrabontás vagy átrendezés legfontosabb tanulsága, hogy a fordító ellenőrzi, hogy a kód kezeli-e az összes lehetséges bemenetet.

Több bemenet

Az eddig tárgyalt minták mindegyike egy bemenetet ellenőrzött. Olyan mintákat írhat, amelyek egy objektum több tulajdonságát vizsgálják. Vegye figyelembe a következő Order rekordot:

public record Order(int Items, decimal Cost);

Az előző pozíciórekordtípus két tagot deklarál explicit pozícióban. Először a Items, majd a sorrend jelenik meg Cost. További információ: Rekordok.

Az alábbi kód a tételek számát és egy kedvezményes ár kiszámítására vonatkozó rendelés értékét vizsgálja:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        { Items: > 10, Cost: > 1000.00m } => 0.10m,
        { Items: > 5, Cost: > 500.00m } => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

Az első két kar a Order. A harmadik csak a költségeket vizsgálja. A következő ellenőrzés az , nullés a végleges megegyezik bármely más érték. Ha a Order típus megfelelő Deconstruct metódust határoz meg, kihagyhatja a tulajdonságneveket a mintából, és dekonstruálással megvizsgálhatja a tulajdonságokat:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        ( > 10,  > 1000.00m) => 0.10m,
        ( > 5, > 50.00m) => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

Az előző kód azt a pozíciómintát mutatja be, ahol a tulajdonságok a kifejezéshez vannak lebontva.

Listaminták

Lista vagy tömb elemeit listaminta használatával ellenőrizheti. A listaminta azt jelenti, hogy egy mintát a sorozat bármely elemére alkalmazhat. Emellett alkalmazhatja az elvetési mintát (_) bármely elemhez, vagy alkalmazhat egy szeletmintát nulla vagy több elemhez.

A listaminták értékes eszköznek számítanak, ha az adatok nem követik a szokásos struktúrát. Mintamegfeleltetéssel tesztelheti az adatok alakját és értékeit ahelyett, hogy objektumkészletté alakítná őket.

Vegye figyelembe a banki tranzakciókat tartalmazó szövegfájl alábbi részletét:

04-01-2020, DEPOSIT,    Initial deposit,            2250.00
04-15-2020, DEPOSIT,    Refund,                      125.65
04-18-2020, DEPOSIT,    Paycheck,                    825.65
04-22-2020, WITHDRAWAL, Debit,           Groceries,  255.73
05-01-2020, WITHDRAWAL, #1102,           Rent, apt, 2100.00
05-02-2020, INTEREST,                                  0.65
05-07-2020, WITHDRAWAL, Debit,           Movies,      12.57
04-15-2020, FEE,                                       5.55

Ez egy CSV formátum, de egyes sorok több oszlopot tartalmaznak, mint mások. A feldolgozás szempontjából még rosszabb, hogy a típus egyik oszlopa WITHDRAWAL felhasználó által létrehozott szöveget tartalmaz, és vesszőt tartalmazhat a szövegben. Listaminta, amely tartalmazza az elvetési mintát, az állandó mintát és a varmintát az érték ilyen formátumban történő rögzítéséhez:

decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{
    balance += transaction switch
    {
        [_, "DEPOSIT", _, var amount]     => decimal.Parse(amount),
        [_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
        [_, "INTEREST", var amount]       => decimal.Parse(amount),
        [_, "FEE", var fee]               => -decimal.Parse(fee),
        _                                 => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),
    };
    Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}

Az előző példa egy sztringtömböt vesz fel, amelyben minden elem egy mező a sorban. A switch második mező kifejezéskulcsai, amelyek meghatározzák a tranzakció típusát és a fennmaradó oszlopok számát. Minden sor biztosítja, hogy az adatok a megfelelő formátumban legyen. Az elvetési minta (_) kihagyja az első mezőt a tranzakció dátumával. A második mező megegyezik a tranzakció típusával. A fennmaradó elem egyezések az összeggel együtt ugorjanak a mezőre. Az utolsó egyezés a var mintával rögzíti az összeg sztringképét. A kifejezés kiszámítja a hozzáadni vagy kivonni kívánt összeget az egyenlegből.

A listaminták lehetővé teszik az adatelemek sorozatának egyezését. Az elvetési és szeletelési mintákat az elemek helyének megfelelően használhatja. Más mintákat használ az egyes elemek jellemzőinek egyeztetéséhez.

Ez a cikk bemutatja, hogy milyen típusú kódokat írhat a C#-beli mintaegyezéssel. Az alábbi cikkek további példákat mutatnak be a minták forgatókönyvekben való használatára, valamint a használható minták teljes szókészletét.

Lásd még