Sdílet prostřednictvím


Přehled porovnávání vzorů

Porovnávání vzorů je technika, při které testujete výraz, abyste zjistili, jestli má určité vlastnosti. Porovnávání vzorů jazyka C# poskytuje stručnější syntaxi pro testování výrazů a provádění akcí, když se výraz shoduje. Výraz "is výraz" podporuje porovnávání vzorů pro otestování výrazu a podmíněně deklaruje novou proměnnou pro výsledek tohoto výrazu. "switch výraz" umožňuje provádět akce na základě prvního odpovídajícího vzoru pro výraz. Tyto dva výrazy podporují bohatou slovní zásobu vzorů.

Tento článek obsahuje přehled scénářů, ve kterých můžete použít porovnávání vzorů. Tyto techniky můžou zlepšit čitelnost a správnost kódu. Úplnou diskuzi o všech vzorech, které můžete použít, najdete v článku o vzorech v referenční dokumentaci jazyka.

Kontrola na nulové hodnoty

Jedním z nejběžnějších scénářů porovnávání vzorů je zajistit, že hodnoty nejsou null. Při testování a převádění nulovatelného hodnotového typu na jeho základní typ můžete použít následující příklad pro testování null:

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");
}

Předchozí kód je vzor deklarace pro otestování typu proměnné a jeho přiřazení k nové proměnné. Pravidla jazyka umožňují tuto techniku lépe zabezpečit než mnoho dalších. Proměnná number je přístupná a přiřazená pouze v pravé části if klauzule. Pokud se ho pokusíte otevřít jinde, buď v else klauzuli, nebo po if bloku, kompilátor vydá chybu. Za druhé, protože nepoužíváte == operátor, tento vzor funguje, když typ přetíží == operátor. Díky tomu je ideální způsob, jak zkontrolovat referenční hodnoty null a přidat not vzor:

string? message = ReadMessageOrDefault();

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

Předchozí příklad použil konstantní vzor k porovnání proměnné s null. Jedná se not o logický vzor , který se shoduje, když se negovaný vzor neshoduje.

Testy typů

Dalším běžným použitím porovnávání vzorů je otestovat proměnnou, abyste zjistili, jestli odpovídá danému typu. Například následující kód testuje, pokud je proměnná nenulová a implementuje System.Collections.Generic.IList<T> rozhraní. Pokud ano, použije ICollection<T>.Count vlastnost v seznamu k vyhledání prostředního indexu. Vzor deklarace neodpovídá hodnotě null bez ohledu na typ kompilace proměnné. Kód níže chrání proti null, a navíc chrání proti typu, který neimplementuje IList.

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();
    }
}

Stejné testy lze použít ve výrazu switch k otestování proměnné na více různých typech. Tyto informace můžete použít k vytvoření lepších algoritmů na základě konkrétního typu běhu.

Porovnání diskrétních hodnot

Můžete také otestovat proměnnou a najít shodu s konkrétními hodnotami. Následující kód ukazuje jeden příklad, ve kterém testujete hodnotu proti všem možným hodnotám deklarovaným ve výčtu:

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)),
   };

Předchozí příklad ukazuje volání metody na základě hodnoty výčtu. Posledním _ případem je vzor pro ignorování, který odpovídá všem hodnotám. Zpracovává všechny chybové podmínky, kdy hodnota neodpovídá jedné z definovaných enum hodnot. Pokud vynecháte toto rameno přepínače, kompilátor upozorní, že výraz vzoru nezpracovává všechny možné vstupní hodnoty. Výraz za switch běhu vyvolá výjimku, pokud se objekt, který je zkoumán, neshoduje s žádnou z větví přepínače. Můžete použít číselné konstanty místo sady výčtu hodnot. Můžete také použít tuto podobnou techniku pro konstantní řetězcové hodnoty, které představují příkazy:

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)),
   };

Předchozí příklad ukazuje stejný algoritmus, ale používá řetězcové hodnoty místo výčtu. Tento scénář byste použili v případě, že aplikace reaguje na textové příkazy namísto běžného formátu dat. Počínaje jazykem C# 11 můžete také použít Span<char> nebo ReadOnlySpan<char>otestovat hodnoty konstantních řetězců, jak je znázorněno v následující ukázce:

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)),
   };

Ve všech těchto příkladech vzor pro ignorování zajistí, abyste zpracovali každý vstup. Kompilátor vám pomůže zajistit, aby byla zpracována každá možná vstupní hodnota.

Relační vzory

Pomocí relačních vzorů můžete otestovat, jak se hodnota porovnává s konstantami. Například následující kód vrátí stav vody na základě teploty v Fahrenheita:

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

Předchozí kód také ukazuje konjunkční andlogický vzor , který kontroluje, že oba relační vzory odpovídají. K ověření shody obou vzorů můžete použít také disjunktivní or vzor. Tyto dva relační vzory jsou obklopeny závorky, které můžete použít kolem jakéhokoli vzoru, aby bylo jasné. Dvě explicitní přepínací ramena (32°F a 212°F) zpracovávají případy pro bod roztavení a bod varu. Bez těchto dvou zbraní vás kompilátor upozorní, že vaše logika nepokrývá všechny možné vstupy.

Předchozí kód také ukazuje další důležitou funkci, kterou kompilátor poskytuje pro výrazy porovnávání vzorů: Kompilátor vás upozorní, pokud nezpracujete každou vstupní hodnotu. Kompilátor také vydá upozornění, pokud je vzor pro větev přepínače pokryt předchozím vzorem. Díky tomu máte volnost přepracovávat a měnit pořadí výrazů přepínače. Dalším způsobem, jak napsat stejný výraz, může být:

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

Klíčovou lekcí v předchozí ukázce a jakékoli jiné refaktoringu nebo změny pořadí je, že kompilátor ověří, že váš kód zpracovává všechny možné vstupy.

Více vstupů

Všechny dosud pokryté vzory kontrolovaly jeden vstup. Můžete psát vzory, které kontrolují více vlastností objektu. Vezměte v úvahu následující Order záznam:

public record Order(int Items, decimal Cost);

Předchozí typ pozičního záznamu deklaruje dva členy na explicitních pozicích. Items se objeví jako první, a poté Cost. Další informace naleznete v tématu Záznamy.

Následující kód prozkoumá počet položek a hodnotu objednávky k výpočtu snížené ceny:

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,
    };

První dvě ramena prověřují dvě vlastnosti Order. Třetí prozkoumá pouze náklady. Další kontroly jsou podle null, a poslední případ odpovídá jakékoli jiné hodnotě. Order Pokud typ definuje vhodnou Deconstruct metodu, můžete vynechat názvy vlastností ze vzoru a pomocí dekonstrukce prozkoumat vlastnosti:

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,
    };

Předchozí kód ukazuje poziční vzor, ve kterém jsou vlastnosti pro výraz dekonstruovány.

Můžete také shodovat vlastnost s { }, která odpovídá jakékoli hodnotě, která není null. Představte si následující deklaraci, která ukládá měření s volitelnou poznámkou:

public record class Observation(int Value, string Units, string Name)
{
    public string? Annotation { get; set; }
}

Pomocí následujícího výrazu porovnávání vzorů můžete otestovat, jestli má dané pozorování nenulovou poznámku:

if (observation.Annotation is { })
{
    Console.WriteLine($"Observation description: {observation.Annotation}");
}

Vzory seznamů

Prvky v seznamu nebo matici můžete zkontrolovat pomocí vzor seznamu. Vzor seznamu poskytuje způsob použití vzoru na libovolný prvek sekvence. Kromě toho můžete použít zahození vzor (_) k odpovídání libovolnému prvku, nebo použít vzor řezu k odpovídání nule nebo více prvkům.

Vzory seznamů jsou cenným nástrojem v případech, kdy data nedodržují běžnou strukturu. Porovnávání vzorů můžete použít k otestování obrazce a hodnot dat místo jejich transformace na sadu objektů.

Podívejte se na následující výňatek z textového souboru obsahujícího bankovní transakce:

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

Jedná se o formát CSV, ale některé řádky mají více sloupců než jiné. Ještě horší zpracování je, že jeden sloupec typu WITHDRAWAL obsahuje uživatelem vygenerovaný text a může v textu obsahovat čárku. Vzor seznamu, který zahrnuje vzor zahození, vzor konstanty a vzor var pro zachycení hodnoty, zpracovává data v tomto formátu:

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}");
}

Předchozí příklad přebírá pole řetězců, kde každý prvek je jedno pole v řádku. Klíče switch výrazů ve druhém poli, které určují druh transakce, a počet zbývajících sloupců. Každý řádek zajišťuje, že data jsou ve správném formátu. Vzor zahození (_) přeskočí první pole, které obsahuje datum transakce. Druhé pole odpovídá typu transakce. Zbývající prvek odpovídá a přeskočí na pole s částkou. Konečný vzor používá var k odchycení řetězcové reprezentace částky. Výraz vypočítá částku k přičtení nebo odečtení ze zůstatku.

Vzory seznamů umožňují shodovat se s tvarem posloupnosti datových prvků. Vzory zahození a řezu použijete k nalezení umístění prvků. K porovnávání charakteristik jednotlivých prvků se používají jiné vzory.

Tento článek poskytl prohlídku typů kódu, které můžete psát pomocí porovnávání vzorů v jazyce C#. V následujících článcích najdete další příklady použití vzorů ve scénářích a úplný slovník vzorů, které lze použít.

Viz také