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 výrazu. 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.

Kontroly null

Jedním z nejběžnějších scénářů porovnávání vzorů je zajistit, že hodnoty nejsou null. Při testování null pomocí následujícího příkladu můžete testovat a převést typ hodnoty null na jeho základní typ:

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 operátor nepoužíváte == , 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í nullproti , kromě ochrany 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 zahození, 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 tuto arm přepínače vynecháte, kompilátor upozorní, že výraz vzoru nezpracuje 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 přepínacích ramen. Místo sady hodnot výčtu můžete použít číselné konstanty. 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 zahození zajistí, že zpracujete 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) and (< 212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid / gas transition",
    };

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é. Poslední dvě ramena spínače 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 se vzor pro arm přepínače vztahuje na předchozí vzor. Díky tomu můžete refaktorovat 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. Objeví se jako první je Items, pak pořadí 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 proti nulla konečný se shoduje s jakoukoli jinou hodnotou. 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 konstruovány.

Vzory seznamů

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

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í , konstantní vzor a vzor var pro zachycení hodnot 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 s datem transakce. Druhé pole odpovídá typu transakce. Zbývající prvek se shoduje s přeskočením pole s částkou. Konečná shoda používá vzor var k zachycení řetězcové reprezentace částky. Výraz vypočítá částku, která se má sčítat nebo odečítat od zůstatku.

Vzory seznamů umožňují shodovat se s tvarem posloupnosti datových prvků. Vzory zahození a řezu použijete ke shodě s umístěním 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é