11 Mönster och mönstermatchning

11.1 Allmänt

Ett mönster är en syntaktisk form som kan användas med operatorn is (§12.14.12), i en switch_statement (§13.8.3) och i en switch_expression (§12.11) för att uttrycka formen på data som inkommande data ska jämföras med. Mönster kan vara rekursiva, så att delar av data kan matchas mot undermönster.

Ett mönster testas mot ett värde i ett antal kontexter:

  • I en switch-instruktion testas mönstret för en ärendeetikett mot uttrycket för switch-instruktionen.
  • I en is-pattern-operator testas mönstret till höger mot uttrycket till vänster.
  • I ett växeluttryck testas mönstret för en switch_expression_arm mot uttrycket på växeluttryckets vänstra sida.
  • I kapslade kontexter testas undermönstret mot värden som hämtats från egenskaper, fält eller indexeras från andra indatavärden, beroende på mönsterformuläret.

Värdet som ett mönster testas mot kallas mönsterindatavärdet.

11.2 Mönsterformulär

11.2.1 Allmänt

Ett mönster kan ha något av följande formulär:

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

Vissa mönsterkan resultera i deklarationen av en lokal variabel.

Varje mönsterformulär definierar uppsättningen med typer för indatavärden som mönstret kan tillämpas på. Ett mönster P gäller för en typ T om T är bland de typer vars värden mönstret kan matcha. Det är ett kompileringsfel om ett mönster P visas i ett program för att matcha ett mönsterindatavärde (§11.1) av typen T om P det inte är tillämpligt på T.

Exempel: I följande exempel genereras ett kompileringsfel eftersom kompileringstidstypen v är TextReader. En variabel av typen TextReader kan aldrig ha ett värde som är referenskompatibelt med 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
}

Följande genererar dock inget kompileringsfel eftersom kompileringstidstypen v är object. En variabel av typen object kan ha ett värde som är referenskompatibelt med string:

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

slutexempel

Varje mönsterformulär definierar den uppsättning värden som mönstret matchar värdet för vid körning.

Ordningen för utvärdering av åtgärder och biverkningar under mönstermatchning (anrop till Deconstruct, egenskapsåtkomster och anrop av metoder i System.ITuple) har inte angetts.

11.2.2 Deklarationsmönster

En declaration_pattern används för att testa att ett värde har en viss typ och, om testet lyckas, om du vill ange värdet i en variabel av den typen.

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

En simple_designation med token _ ska betraktas som en discard_designation snarare än en single_variable_designation.

Körningstypen för värdet testas mot typen i mönstret med samma regler som anges i operatorn is-type (§12.14.12.1). Om testet lyckas matchar mönstret det värdet. Det är ett kompileringsfel om typen är en nullbar värdetyp (§8.3.12) eller en nullbar referenstyp (§8.9.3). Det här mönsterformuläret matchar aldrig ett null värde.

Obs! Uttrycket e is T is-type och deklarationsmönstret e is T _ är likvärdiga när T det inte är en nullbar typ. slutkommentar

Med tanke på ett mönsterindatavärde (§11.1) e, om simple_designation är discard_designation, anger det ett ignorerande (§9.2.9.2), och värdet på e är inte bundet till någonting. (Även om en deklarerad variabel med namnet _ kan finnas i omfånget vid den tidpunkten visas inte den namngivna variabeln i den här kontexten.) Om simple_designation annars är single_variable_designation introduceras en lokal variabel (§9.2.9) av den angivna typen som namnges av den angivna identifieraren. Den lokala variabeln tilldelas värdet för mönsterindatavärdet när mönstret matchar värdet.

Vissa kombinationer av statisk typ av mönsterindatavärdet och den angivna typen anses vara inkompatibla och resulterar i ett kompileringsfel. Ett värde av statisk typ E sägs vara mönsterkompatibelt med typen T om det finns en identitetskonvertering, en implicit eller explicit referenskonvertering, en boxningskonvertering eller en avboxningskonvertering från E till T, eller om någon E av dem eller T är en öppen typ (§8.4.3). Ett deklarationsmönster som namnger en typ gäller för varje typ T som är mönsterkompatibel med E.ET

Obs! Stödet för öppna typer kan vara mest användbart när du kontrollerar typer som kan vara antingen struct- eller klasstyper, och boxning bör undvikas. slutkommentar

Exempel: Deklarationsmönstret är användbart för att utföra körningstyptester av referenstyper och ersätter formspråket

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

med den något mer koncisa

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

slutexempel

Exempel: Deklarationsmönstret kan användas för att testa värden för null-typer: ett värde av typen Nullable<T> (eller en rutad T) matchar ett typmönster T2 id om värdet inte är null och T2 är T, eller någon bastyp eller gränssnitt för T. Till exempel i kodfragmentet

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

Villkoret för -instruktionen if är true vid körning och variabeln v innehåller värdet 3 av typen int i blocket. Efter blocket är variabeln v i omfånget, men inte definitivt tilldelad. slutexempel

11.2.3 Konstant mönster

En constant_pattern används för att testa värdet för ett mönsterindatavärde (§11.1) mot det angivna konstantvärdet.

constant_pattern
    : constant_expression
    ;

Ett konstant mönster P gäller för en typ T om det finns en implicit konvertering från det konstanta uttrycket för P till typen T.

För ett konstant mönster Pär dess konverterade värde

  • Om mönstrets indatavärdestyp är en integraltyp eller en uppräkningstyp konverteras mönstrets konstantvärde till den typen. annars
  • Om mönstrets indatavärdestyp är den nullbara versionen av en integraltyp eller en uppräkningstyp konverteras mönstrets konstantvärde till dess underliggande typ. annars
  • värdet för mönstrets konstanta värde.

Givet ett mönsterindatavärde e och ett konstant mönster P med konverterat värde v,

  • om e har en integraltyp eller uppräkningstyp, eller en nullbar form av en av dessa, och v har en integrerad typ, matchar mönstret Pvärdet e om resultatet av uttrycket e == v är true, annars
  • mönstret Pmatchar värdet e om object.Equals(e, v) returnerar true.

Exempel: Instruktionen switch i följande metod använder fem konstanta mönster i sina skiftlägesetiketter.

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

slutexempel

11.2.4 Var-mönster

Ett var_patternmatchar varje värde. En mönstermatchningsåtgärd med en var_pattern lyckas alltså alltid.

En var_pattern gäller för varje typ.

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

Givet ett mönstrat indatavärde (§11.1) e, om beteckningenär discard_designation, det antecknar en kassera (§9.2.9.2), och värdera av e är inte bundet till något. (Även om en deklarerad variabel med det namnet kan finnas i omfånget vid den tidpunkten visas inte den namngivna variabeln i den här kontexten.) Annars, om beteckningenär single_variable_designation, vid körning är värdet e bundet till en nyligen introducerad lokal variabel (§9.2.9) av det namnet vars typ är den statiska typen av e, och mönstrets indatavärde tilldelas till den lokala variabeln.

Det är ett fel om namnet var skulle binda till en typ där en var_pattern används.

Om beteckning är en tuple_designation, är mönstret likvärdigt med en positional_pattern (§11.2.5) av formbeteckningen(var, ... ) där beteckningenär de som finns inom tuple_designation. Mönstret var (x, (y, z)) motsvarar till (var x, (var y, var z))exempel .

11.2.5 Positionsmönster

En positional_pattern kontrollerar att indatavärdet inte nullär , anropar en lämplig Deconstruct metod (§12.7) och utför ytterligare mönstermatchning på de resulterande värdena. Den stöder också en tupplarliknande mönstersyntax (utan att typen anges) när typen av indatavärde är samma som typen som innehåller Deconstruct, eller om typen av indatavärde är en tupplartyp, eller om typen av indatavärde är object eller System.ITuple och körningstypen för uttrycket implementerar System.ITuple.

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

Med tanke på en matchning av ett indatavärde med mönstertypens(undermönster), väljs en metod genom att söka i typ efter tillgängliga deklarationer av Deconstruct och välja en av dem med samma regler som för dekonstruktionsdeklarationen. Det är ett fel om en positional_pattern utelämnar typen, har ett enda undermönster utan identifierare, inte har någon property_subpattern och inte har någon simple_designation. Detta skiljer sig mellan en constant_pattern som är parentesiserad och en positional_pattern. För att extrahera de värden som ska matchas mot mönstren i listan,

  • Om typen utelämnas och indatauttryckets typ är en tuppelns typ, ska antalet undermönster vara detsamma som tuppelns kardinalitet. Varje tuppelns element matchas mot motsvarande underavsnitt och matchningen lyckas om alla dessa lyckas. Om någon underordnad har en identifierare, ska det namnge ett tupppelelement vid motsvarande position i tuppelns typ.
  • Om en lämplig Deconstruct finns som medlem av typen är det annars ett kompileringsfel om typen av indatavärde inte är mönsterkompatibel med typen. Vid körning testas indatavärdet mot typ. Om detta misslyckas misslyckas positionsmönstermatchningen. Om det lyckas konverteras indatavärdet till den här typen och Deconstruct anropas med nya kompilatorgenererade variabler för att ta emot utdataparametrarna. Varje värde som togs emot matchas mot motsvarande undermönster och matchningen lyckas om alla dessa lyckas. Om någon underordnad har en identifierare ska den ge en parameter namnet på motsvarande position Deconstruct.
  • Annars, om typen utelämnas och indatavärdet är av typen object eller någon typ som kan konverteras till System.ITuple av en implicit referenskonvertering, och ingen identifierare visas bland underavsnitten, använder System.ITuplematchningen .
  • Annars är mönstret ett kompileringsfel.

Ordningen i vilken underavsnitt matchas vid körning är ospecificerad och en misslyckad matchning kanske inte försöker matcha alla undermönster.

Exempel: Här dekonstruerar vi ett uttrycksresultat och matchar de resulterande värdena mot motsvarande kapslade mönster:

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

slutexempel

Exempel: Namnen på tuppelns element och dekonstruktionsparametrar kan användas i ett positionsmönster enligt följande:

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

De utdata som genereras är

Sum of [10 20 30] is 60

slutexempel

11.2.6 Egenskapsmönster

En property_pattern kontrollerar att indatavärdet inte nullär och matchar rekursivt värden som extraheras med hjälp av tillgängliga egenskaper eller fält.

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

Det är ett fel om ett underavsnitt av en property_pattern inte innehåller någon identifierare.

Det är ett kompileringsfel om typen är en nullbar värdetyp (§8.3.12) eller en nullbar referenstyp (§8.9.3).

Obs! Ett null-kontrollmönster faller ur ett trivialt egenskapsmönster. För att kontrollera om strängen s inte är null kan man skriva något av följande formulär:

#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 {}) ...

slutkommentar Med tanke på en matchning av ett uttryck e till mönstertypen{property_pattern_list} är det ett kompileringsfel om uttrycket e inte är mönsterkompatibelt med typen T som anges efter typ. Om typen saknas antas typen vara den statiska typen e. Var och en av de identifierare som förekommer till vänster i dess property_pattern_list skall ange en tillgänglig läsbar egenskap eller ett fält i T. Om simple_designation för property_pattern finns deklarerar den en mönstervariabel av typen T.

Vid körning testas uttrycket mot T. Om detta misslyckas misslyckas egenskapsmönstermatchningen och resultatet blir false. Om det lyckas läss varje property_subpattern fält eller egenskap och dess värde matchas mot motsvarande mönster. Resultatet av hela matchningen är false bara om resultatet av något av dessa är false. Ordningen där undermönster matchas anges inte och en misslyckad matchning kanske inte testar alla undermönster vid körning. Om matchningen lyckas och simple_designation för property_pattern är en single_variable_designation tilldelas den deklarerade variabeln det matchade värdet.

Property_pattern kan användas för att mönstermatcha med anonyma typer.

Exempel:

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

slutexempel

Exempel: En körningstypkontroll och en variabeldeklaration kan läggas till i ett egenskapsmönster enligt följande:

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

De utdata som genereras är

Hello
Hi!
12345
abc

slutexempel

11.2.7 Ignorera mönster

Varje uttryck matchar mönstret ignorera, vilket resulterar i värdet för uttrycket som ignoreras.

discard_pattern
    : '_'
    ;

Det är ett kompileringsfel att använda ett ignorerande mönster i en relational_expression av formuläret relational_expressionismönster eller som mönstret för en switch_label.

Obs! Om du vill matcha alla uttryck använder du en var_pattern med ignorera var _. slutkommentar

Exempel:

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

De utdata som genereras är

5.0
0.0
0.0

Här används ett ignorerande mönster för att hantera null och alla heltalsvärden som inte har motsvarande medlem i DayOfWeek uppräkningen. Det garanterar att uttrycket switch hanterar alla möjliga indatavärden. slutexempel

11.3 Undersummor för mönster

I en switch-instruktion är det ett fel om ett ärendes mönster undersummas av föregående uppsättning obevakade ärenden (§13.8.3). Informellt innebär detta att alla indatavärden skulle ha matchats av ett av de tidigare fallen. Följande regler definierar när en uppsättning mönster undersummar ett givet mönster:

Ett mönster Pskulle matcha en konstant K om specifikationen för det mönstrets körningsbeteende är som P matchar K.

En uppsättning mönster Qundersummar ett mönster P om något av följande villkor gäller:

11.4 Mönster fullständighet

Informellt är en uppsättning mönster uttömmande för en typ om, för varje möjligt värde av den typen förutom null, något mönster i uppsättningen är tillämpligt. Följande regler definierar när en uppsättning mönster är uttömmande för en typ:

En uppsättning mönster Q är fullständig för en typ T om något av följande villkor gäller:

  1. T är en integrerad eller uppräkningstyp, eller en nullbar version av en av dessa, och för varje möjligt värde av Tden underliggande typen som inte kan nollföras skulle något mönster i Q matcha det värdet, eller
  2. Vissa mönster i Q är ett var-mönster, eller
  3. Vissa mönster i Q är ett deklarationsmönster för typen D, och det finns en identitetskonvertering, en implicit referenskonvertering eller en boxningskonvertering från T till D.

Exempel:

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

slutexempel