Sdílet prostřednictvím


11 Vzory a porovnávání vzorů

11.1 Obecné

Vzor je syntaktická forma, kterou lze použít s operátorem is (§12.14.12), v switch_statement (§13.8.3) a v switch_expression (§12.11) vyjádřit tvar dat, proti kterým se mají příchozí data porovnávat. Vzory můžou být rekurzivní, takže se části dat můžou shodovat s dílčími vzory.

Model se testuje s hodnotou v řadě kontextů:

  • V příkazu switch se vzor popisku případu testuje s výrazem příkazu switch.
  • V operátoru is-pattern je vzor na pravé straně testován proti výrazu vlevo.
  • Ve výrazu switch se vzorswitch_expression_arm testuje s výrazem na levé straně výrazu switch-expression.
  • V vnořených kontextech se dílčí vzor testuje na základě hodnot načtených z vlastností, polí nebo indexovaných z jiných vstupních hodnot v závislosti na formuláři vzoru.

Hodnota, proti které se testuje, se nazývá vstupní hodnota vzoru.

11.2 Vzorové formuláře

11.2.1 Obecné

Vzor může mít jednu z následujících forem:

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    ;

Některé vzorymůžou vést k deklaraci místní proměnné.

Každý formulář vzoru definuje sadu typů pro vstupní hodnoty, na které lze vzor použít. Vzor P se vztahuje na typ T , pokud T patří mezi typy, jejichž hodnoty se vzor může shodovat. Jedná se o chybu v době kompilace, pokud se v programu objeví vzorP, který odpovídá vstupní hodnotě vzoru (§11.1 pokud T není použitelný pro P.

Příklad: Následující příklad vygeneruje chybu v době kompilace, protože typ v kompilace je TextReader. Proměnná typu TextReader nemůže mít nikdy hodnotu, která je kompatibilní s string:

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

Následující příkaz však nevygeneruje chybu v době kompilace, protože typ v kompilace je object. Proměnná typu object může mít hodnotu, která je kompatibilní s string:

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

konec příkladu

Každý formulář vzoru definuje sadu hodnot, pro které vzor odpovídá hodnotě za běhu.

Pořadí vyhodnocení operací a vedlejších účinků během porovnávání vzorů (volání Deconstruct, přístupů k vlastnostem a vyvolání metod v System.ITuple) není zadáno.

11.2.2 Model deklarace

Declaration_pattern slouží k otestování, že hodnota má daný typ, a pokud je test úspěšný, volitelně zadat hodnotu v proměnné tohoto typu.

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

Simple_designation s tokenem _ se považuje za discard_designation, nikoli single_variable_designation.

Typ modulu runtime hodnoty se testuje s typem ve vzoru pomocí stejných pravidel uvedených v operátoru is-type (§12.14.12.1). Pokud je test úspěšný, vzor odpovídá této hodnotě. Jedná se o chybu v době kompilace, pokud je typ hodnoty null (§8.3.12) nebo odkazový typ s možnou hodnotou null (§8.9.3). Tento vzorový formulář nikdy neodpovídá hodnotě null .

Poznámka: Výraz e is T is-type a vzor e is T _ deklarace jsou ekvivalentní, pokud T není typu s možnou hodnotou null. koncová poznámka

Při zadání vzorové vstupní hodnoty (§11.1) e, pokud je simple_designationdiscard_designation, označuje zahození (§9.2.9.2) a hodnota e není nijak vázána. (I když deklarovaná proměnná s názvem _ může být v tomto okamžiku v oboru, tato pojmenovaná proměnná se v tomto kontextu nezobrazí.) Pokud je simple_designationsingle_variable_designation, zanese se místní proměnná (§9.2.9) daného typu pojmenovaného daným identifikátorem. Tato místní proměnná má přiřazenou hodnotu vstupní hodnoty vzoru, pokud vzor odpovídá hodnotě.

Určité kombinace statického typu vstupní hodnoty vzoru a daného typu jsou považovány za nekompatibilní a výsledkem chyby v době kompilace. Hodnota statického typu E je oznamována jako vzorek kompatibilní s typemT, pokud existuje převod identity, implicitní nebo explicitní převod odkazu, převod boxingu nebo zrušení převodu z E do T, nebo je-li otevřený ET typ (§8.4.3). Deklarace vzoru pojmenování typu T je použitelná pro každý typ E , pro který E je vzor kompatibilní s T.

Poznámka: Podpora otevřených typů může být nejužitečnější při kontrole typů, které mohou být buď struktury, nebo typy tříd, a boxování je třeba se vyhnout. koncová poznámka

Příklad: Model deklarace je užitečný pro provádění testů typů běhu referenčních typů a nahrazuje idiom.

var v = expr as Type;
if (v != null) { /* code using v */ }

s mírně stručnější

if (expr is Type v) { /* code using v */ }

konec příkladu

Příklad: Vzor deklarace lze použít k otestování hodnot typů s možnou hodnotou null: hodnota typu (nebo boxedNullable<T>) odpovídá vzoru TT2 id typu, pokud hodnota není null a T2 je T, nebo některé základního typu nebo rozhraní T. Například v fragmentu kódu

int? x = 3;
if (x is int v) { /* code using v */ }

Podmínka if příkazu je true za běhu a proměnná v obsahuje hodnotu 3 typu int uvnitř bloku. Po bloku je proměnná v v oboru, ale rozhodně není přiřazena. konec příkladu

11.2.3 Konstantní vzor

Constant_pattern se používá k otestování hodnoty vstupní hodnoty vzoru (§11.1) s danou konstantní hodnotou.

constant_pattern
    : constant_expression
    ;

Konstantní vzor P se vztahuje na typ T , pokud existuje implicitní převod z konstantního výrazu P na typ T.

U konstantního vzoru Pje převedená hodnota

  • je-li typ vstupní hodnoty vzoru celočíselný typ nebo výčtový typ, konstantní hodnota vzoru převedena na tento typ; jinak
  • pokud typ vstupní hodnoty vzoru je nullable verze celočíselného typu nebo výčtu typ, konstantní hodnota vzoru převedena na jeho základní typ; jinak
  • hodnota konstantní hodnoty vzoru.

Při zadání vstupní hodnoty vzoru e a konstantního vzoru P s převedenou hodnotou v,

  • pokud e má celočíselný typ nebo výčtový typ nebo nulovou formu jednoho z nich a v má celočíselný typ, vzor Podpovídá hodnotě e, pokud je e == vvýsledek výrazu true ;
  • vzor P odpovídá hodnotě e, pokud se vrátí object.Equals(e, v).true

Příklad: Příkaz switch v následující metodě používá pět konstantních vzorů v popiscích písmen.

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

konec příkladu

11.2.4 Vzor var

Var_pattern odpovídá každé hodnotě. To znamená, že operace porovnávání vzorů s var_pattern vždy proběhne úspěšně.

Var_pattern platí pro každý typ.

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

Při zadání vzorové vstupní hodnoty (§11.1) e, pokud je označenídiscard_designation, označuje zahození (§9.2.9.2) a hodnota e není vázána na nic. (I když deklarovaná proměnná s tímto názvem může být v tomto okamžiku v oboru, tato pojmenovaná proměnná se v tomto kontextu nezobrazí.) V opačném případě, pokud je označenísingle_variable_designation, je hodnota e vázána na nově zavedenou místní proměnnou (§9.2.9) tohoto názvu, jehož typ je statickým typem e, a vstupní hodnota vzoru je přiřazena k této místní proměnné.

Jedná se o chybu, pokud by se název var sváže s typem, ve kterém se používá var_pattern .

Je-li označenítuple_designation, je vzor ekvivalentní positional_pattern (§11.2.5) označení formuláře(var, ... ) kde jsou označenínalezena v tuple_designation. Například vzor var (x, (y, z)) je ekvivalentní (var x, (var y, var z)).

11.2.5 Poziční vzor

Positional_pattern zkontroluje, zda vstupní hodnota není null, vyvolá odpovídající Deconstruct metodu (§12.7) a provede další porovnávání vzorů s výslednými hodnotami. Podporuje také syntaxi vzoru řazenou kolekci členů (bez zadaného typu), pokud je typ vstupní hodnoty stejný jako typ obsahující Deconstruct, nebo pokud typ vstupní hodnoty je typ řazené kolekce členů, nebo pokud typ vstupní hodnoty je object nebo System.ITuple a typ modulu runtime výraz implementuje System.ITuple.

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | identifier ':' pattern
    ;

Vzhledem k porovnání vstupní hodnoty spodpatterny)typu( vzoru je metoda vybrána tak, že vyhledá v typu přístupné deklarace Deconstruct a vybere jednu z nich pomocí stejných pravidel jako pro dekonstrukční deklaraci. Jedná se o chybu, pokud positional_pattern vynechá typ, má jeden podpattern bez identifikátoru, nemá žádné property_subpattern a nemá žádné simple_designation. To se nejednoznačným způsobem liší mezi constant_pattern , která je závorka a positional_pattern. Chcete-li extrahovat hodnoty, které se mají shodovat se vzory v seznamu,

  • Pokud je typ vynechán a typ vstupního výrazu je typ řazené kolekce členů, bude počet podpatternů stejný jako kardinalita řazené kolekce členů. Každý prvek řazené kolekce členů se porovná s odpovídajícím podpatternem a shoda bude úspěšná, pokud všechny tyto prvky proběhnou úspěšně. Pokud má některý podpatternidentifikátor, pak tento výraz pojmenuje prvek řazené kolekce členů na odpovídající pozici v typu řazené kolekce členů.
  • Jinak, pokud vhodný Deconstruct existuje jako člen typu, jedná se o chybu v době kompilace, pokud typ vstupní hodnoty není kompatibilní s typem. Za běhu se vstupní hodnota testuje proti typu. Pokud se to nezdaří, shoda pozičního vzoru selže. Pokud bude úspěšné, vstupní hodnota se převede na tento typ a Deconstruct vyvolá se s čerstvými proměnnými generovanými kompilátorem pro příjem výstupních parametrů. Každá přijatá hodnota se shoduje s odpovídajícím podpatternem a shoda proběhne úspěšně, pokud jsou všechny tyto hodnoty úspěšné. Pokud má některý dílčí podpatternidentifikátor, pak tento parametr pojmenuje na odpovídající pozici Deconstruct.
  • V opačném případě, pokud je typ vynechán a vstupní hodnota je typu object nebo určitý typ, který lze převést System.ITuple na implicitní převod odkazu, a žádný identifikátor se nezobrazí mezi podpatterny, pak shoda použije System.ITuple.
  • V opačném případě se jedná o chybu v době kompilace.

Pořadí, ve kterém se podpatterny shodují za běhu, není zadáno a neúspěšná shoda se nemusí pokusit spárovat všechny podpatterny.

Příklad: Tady dekonstruujeme výsledek výrazu a porovnáme výsledné hodnoty s odpovídajícími vnořenými vzory:

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

konec příkladu

Příklad: Názvy elementů řazené kolekce členů a parametry dekonstrukce lze použít v pozičním vzoru následujícím způsobem:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndCount(numbers) is (Sum: var sum, Count: var count))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

Výstup je vytvořen.

Sum of [10 20 30] is 60

konec příkladu

11.2.6 Vzor vlastností

Property_pattern zkontroluje, že vstupní hodnota není null, a rekurzivně odpovídá hodnotám extrahovaným použitím přístupných vlastností nebo polí.

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

Jedná se o chybu, pokud některý dílčí podpatternproperty_pattern neobsahuje identifikátor.

Jedná se o chybu v době kompilace, pokud je typ hodnoty null (§8.3.12) nebo odkazový typ s možnou hodnotou null (§8.9.3).

Poznámka: Vzor kontroly hodnoty null se nevyučuje ze vzoru triviální vlastnosti. Pokud chcete zkontrolovat, jestli je řetězec s nenulový, můžete napsat některý z následujících formulářů:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

koncová poznámka Vzhledem k porovnání výrazu e s typem{ vzoruproperty_pattern_list} se jedná o chybu v době kompilace, pokud výraz e není kompatibilní se typem T určeným typem. Pokud typ chybí, předpokládá se, že se jedná o statický typ e. Každý identifikátor, který se nachází na levé straně jeho property_pattern_list, určí přístupnou čitelnou vlastnost nebo pole T. Pokud simple_designation property_pattern existuje, deklaruje proměnnou vzoru typu T.

Za běhu se výraz testuje proti T. Pokud se to nezdaří, vzor vlastnosti se nezdaří a výsledek je false. Pokud je to úspěšné, každé property_subpattern pole nebo vlastnost se přečte a jeho hodnota se shoduje s odpovídajícím vzorem. Výsledek celé shody je false pouze v případě, že výsledek některého z těchto výsledků je false. Pořadí, ve kterém se podpatterny shodují, není zadáno a neúspěšná shoda nemusí testovat všechny podpatterny za běhu. Pokud je shoda úspěšná a simple_designationproperty_pattern je single_variable_designation, deklarovaná proměnná se přiřadí odpovídající hodnotě.

Property_pattern lze použít k porovnávání vzorů s anonymními typy.

Příklad:

var o = ...;
if (o is string { Length: 5 } s) ...

konec příkladu

Příklad: Kontrola typu za běhu a deklarace proměnné lze přidat do vzoru vlastnosti následujícím způsobem:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));            // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,
    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),
    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

Výstup je vytvořen.

Hello
Hi!
12345
abc

konec příkladu

11.2.7 Zahodit vzor

Každý výraz odpovídá vzoru zahození, což vede k zahození hodnoty výrazu.

discard_pattern
    : '_'
    ;

Jedná se o chybu v době kompilace použití vzoru zahození v relational_expressionrelational_expression formulářeisnebo jako vzor switch_label.

Poznámka: V těchto případech, aby se shodovaly s libovolným výrazem, použijte var_pattern s zahozením var _. koncová poznámka

Příklad:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

Výstup je vytvořen.

5.0
0.0
0.0

Zde se vzor zahození používá ke zpracování null a jakékoli celočíselné hodnotě, která nemá odpovídající člen výčtu DayOfWeek . To zaručuje, že switch výraz zpracovává všechny možné vstupní hodnoty. konec příkladu

11.3 Dílčí podsumpce vzorku

V příkazu switch se jedná o chybu, pokud je vzor případu podsoučet předchozí sadou nehlídaných případů (§13.8.3). Neformálně to znamená, že jakákoli vstupní hodnota by se shodovala s jednou z předchozích případů. Následující pravidla definují, kdy sada vzorů podsoustaví daný vzor:

Vzor Pby se shodoval s konstantou K v případě, že P specifikace chování modulu runtime daného vzoru odpovídá K.

Sada vzorů Qpodsoustaví vzor P , pokud se drží některé z následujících podmínek:

11.4 Úplnost vzoru

Neformálně je sada vzorů pro typ vyčerpávající, pokud platí pro každou možnou hodnotu tohoto typu kromě hodnoty null, je možné použít nějaký vzor v sadě. Následující pravidla definují, kdy je sada vzorů pro typ vyčerpávající :

Sada vzorů Q je , pokud jsou splněny některé z následujících podmínek:

  1. T je celočíselný nebo výčtový typ nebo verze jednoho z nich s možnou hodnotou null a pro každou možnou hodnotu základního Ttypu, která Q není nullable, by se určitý vzor shodoval s danou hodnotou;
  2. Určitý vzor v Q je var vzor; nebo
  3. V některých vzorech Q je vzor deklarace pro typ Da existuje převod identity, implicitní převod odkazu nebo krabicový převod z T na D.

Příklad:

static void M(byte b)
{
    switch (b) {
        case 0: case 1: case 2: ... // handle every specific value of byte
            break;
        // error: the pattern 'byte other' is subsumed by the (exhaustive)
        // previous cases
        case byte other: 
            break;
    }
}

konec příkladu