Freigeben über


11 Muster und Musterabgleich

11.1 Allgemein

Ein Muster ist eine syntaktische Form, die mit dem is Operator (§12.14.12) in einem switch_statement (§13.8.3) und in einem switch_expression (§12.11) verwendet werden kann, um das Shape der Daten auszudrücken, mit denen eingehende Daten verglichen werden sollen. Muster können rekursiv sein, sodass Teile der Daten mit Untermustern abgeglichen werden können.

Ein Muster wird anhand eines Werts in einer Reihe von Kontexten getestet:

  • In einer Switch-Anweisung wird das Muster einer Fallbezeichnung anhand des Ausdrucks der Switch-Anweisung getestet.
  • Bei einem Is-Pattern-Operator wird das Muster auf der rechten Seite mit dem Ausdruck auf der linken Seite getestet.
  • In einem Schalterausdruck wird das Muster eines switch_expression_arm auf der linken Seite des Schalterausdrucks anhand des Ausdrucks getestet.
  • In geschachtelten Kontexten wird das Untermuster abhängig vom Musterformular anhand von Werten getestet, die aus Eigenschaften, Feldern oder aus anderen Eingabewerten indiziert wurden.

Der Wert, für den ein Muster getestet wird, wird als Mustereingabewert bezeichnet.

11.2 Musterformulare

11.2.1 Allgemein

Ein Muster kann eine der folgenden Formen aufweisen:

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

Einige Musterkönnen zur Deklaration einer lokalen Variablen führen.

Jedes Musterformular definiert den Satz von Typen für Eingabewerte, auf die das Muster angewendet werden kann. Ein Muster P gilt für einen Typ T , wenn T es sich um die Typen handelt, deren Werte das Muster möglicherweise abgleichen kann. Es handelt sich um einen Kompilierungsfehler, wenn ein Muster P in einem Programm angezeigt wird, um einem Mustereingabewert (§11.1) des Typs T zu entsprechen, falls P nicht zutreffend T.

Beispiel: Im folgenden Beispiel wird ein Kompilierungszeitfehler generiert, da der Kompilierungszeittyp v lautet TextReader. Eine Variable vom Typ TextReader kann niemals einen Wert aufweisen, der referenzkompatibel ist mit 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
}

Im Folgenden wird jedoch kein Kompilierungszeitfehler generiert, da der Kompilierungszeittyp v lautet object. Eine Variable des Typs object könnte einen Wert aufweisen, der referenzkompatibel ist mit string:

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

Endbeispiel

Jedes Musterformular definiert den Wertesatz, für den das Muster dem Wert zur Laufzeit entspricht .

Die Reihenfolge der Auswertung von Vorgängen und Nebenwirkungen während des Musterabgleichs (Aufrufe Deconstruct, Eigenschaftenzugriffe und Aufrufe von Methoden in System.ITuple) wird nicht angegeben.

11.2.2 Deklarationsmuster

Ein declaration_pattern wird verwendet, um zu testen, ob ein Wert einen bestimmten Typ aufweist, und wenn der Test erfolgreich ist, um optional den Wert in einer Variablen dieses Typs bereitzustellen.

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

Ein simple_designation mit dem Token _ gilt als discard_designation und nicht als single_variable_designation.

Der Laufzeittyp des Werts wird anhand der gleichen Regeln getestet, die im Is-Type-Operator (§12.14.12.12.1) angegeben sind. Wenn der Test erfolgreich ist, entspricht das Muster diesem Wert. Es handelt sich um einen Kompilierungszeitfehler, wenn der Typ ein Nullwerttyp (§8.3.12) oder ein nullabler Bezugstyp (§8.9.3) ist. Dieses Musterformular entspricht niemals einem null Wert.

Hinweis: Der Is-Type-Ausdruck e is T und das Deklarationsmuster e is T _ sind gleichwertig, wenn T kein Nullwerttyp vorhanden ist. Endnote

Bei einem Mustereingabewert (§11.1) e wird, wenn die simple_designationdiscard_designation ist, ein Verwerfen (§9.2.9.2) bezeichnet, und der Wert von e ist an nichts gebunden. (Obwohl eine deklarierte Variable mit dem Namen _ zu diesem Zeitpunkt im Bereich enthalten sein kann, wird diese benannte Variable in diesem Kontext nicht angezeigt.) Andernfalls wird eine lokale Variable (§9.2.9) des angegebenen Typs eingeführt, wenn der simple_designationsingle_variable_designation ist. Diese lokale Variable wird dem Wert des Mustereingabewerts zugewiesen, wenn das Muster dem Wert entspricht .

Bestimmte Kombinationen des statischen Typs des Mustereingabewerts und des angegebenen Typs werden als inkompatibel betrachtet und führen zu einem Kompilierungszeitfehler. Ein Wert des statischen Typs E wird als Muster angegeben, das mit dem Typ kompatibel, wenn eine Identitätskonvertierung, eine implizite oder explizite Verweiskonvertierung, eine Boxkonvertierung oder eine Unboxing-Konvertierung von T zu E, oder wenn TE es sich um einen geöffneten Typ (T) handelt. Ein Deklarationsmuster, das einen Typ T benennt, gilt für jeden Typ E , für den E das Muster kompatibel ist T.

Hinweis: Die Unterstützung für offene Typen kann am nützlichsten sein, wenn Typen überprüft werden, die entweder Struktur- oder Klassentypen sein können, und boxen ist zu vermeiden. Endnote

Beispiel: Das Deklarationsmuster ist nützlich, um Laufzeittypentests von Referenztypen durchzuführen und das Idiom zu ersetzen.

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

mit der etwas prägnanteren

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

Endbeispiel

Beispiel: Das Deklarationsmuster kann verwendet werden, um Werte von nullablen Typen zu testen: Ein Wert vom Typ Nullable<T> (oder ein Boxed T) entspricht einem Typmuster T2 id , wenn der Wert ungleich NULL ist und T2Toder ein Basistyp oder eine Schnittstelle von T. Beispiel: im Codefragment

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

Die Bedingung der if Anweisung befindet true sich zur Laufzeit, und die Variable v enthält den Wert 3 des Typs int innerhalb des Blocks. Nach dem Block befindet sich die Variable v im Bereich, aber nicht definitiv zugewiesen. Endbeispiel

11.2.3 Konstantenmuster

Ein constant_pattern wird verwendet, um den Wert eines Mustereingabewerts (§11.1) anhand des angegebenen Konstantenwerts zu testen.

constant_pattern
    : constant_expression
    ;

Ein Konstantenmuster P gilt für einen Typ T , wenn eine implizite Konvertierung vom konstanten Ausdruck in P den Typ Tvorhanden ist.

Bei einem Konstantenmuster Pist der konvertierte Wert

  • wenn der Typ des Mustereingabewerts ein integraler Typ oder ein Enumerationstyp ist, wird der Konstantenwert des Musters in diesen Typ konvertiert; sonst
  • wenn der Typ des Mustereingabewerts die NULL-Version eines integralen Typs oder eines Enumerationstyps ist, wird der konstanten Wert des Musters in den zugrunde liegenden Typ konvertiert; sonst
  • der Wert des Konstantenwerts des Musters.

Bei einem Mustereingabewert e und einem konstanten Muster P mit konvertiertem Wert v

  • wenn e einen integralen Typ oder Enumerationstyp hat oder eine nullable Form eines dieser Typen aufweist und v einen integralen Typ aufweist, entsprichtP dem Wert e, wenn das Ergebnis des Ausdrucks e == v lautettrue; andernfalls
  • das Muster Pentspricht dem Wert e , wenn object.Equals(e, v) zurückgegeben wird true.

Beispiel: Die switch Anweisung in der folgenden Methode verwendet fünf Konstantenmuster in ihren Fallbeschriftungen.

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

Endbeispiel

11.2.4 Var-Muster

Ein var_patternentspricht jedem Wert. Das heißt, ein Musterabgleichsvorgang mit einem var_pattern ist immer erfolgreich.

Ein var_pattern gilt für jeden Typ.

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

Bei einem Mustereingabewert (§11.1) e, wenn die Bezeichnungdiscard_designation ist, wird ein Verwerfen (§9.2.9.2) bezeichnet, und der Wert von e ist an nichts gebunden. (Obwohl eine deklarierte Variable mit diesem Namen zu diesem Zeitpunkt im Bereich enthalten sein kann, wird diese benannte Variable in diesem Kontext nicht angezeigt.) Andernfalls ist die Bezeichnungsingle_variable_designation, zur Laufzeit ist der Wert von e an eine neu eingeführte lokale Variable (§9.2.9) dieses Namens gebunden, deren Typ der statische Typ von e ist, und der Mustereingabewert dieser lokalen Variablen zugewiesen wird.

Es ist ein Fehler, wenn der Name var an einen Typ gebunden würde, in dem ein var_pattern verwendet wird.

Wenn die Bezeichnung eine tuple_designation ist, entspricht das Muster einem positional_pattern (§11.2.5) der Formularbezeichnung(var, ... ) sind die Bezeichnungeninnerhalb der tuple_designation. Das Muster var (x, (y, z)) entspricht (var x, (var y, var z))z. B. .

11.2.5 Positionsmuster

Ein positional_pattern überprüft, ob der Eingabewert nicht nullist, ruft eine geeignete Deconstruct Methode (§12.7) auf und führt einen weiteren Musterabgleich für die resultierenden Werte durch. Es unterstützt auch eine Tupel-ähnliche Mustersyntax (ohne den Typ, der bereitgestellt wird), wenn der Typ des Eingabewerts mit dem Typ übereinstimmt, der den Typ des Eingabewerts enthältDeconstruct, oder wenn der Typ des Eingabewerts ein Tupeltyp ist oder ob der Typ des Eingabewerts oder System.ITuple der Laufzeittyp des Ausdrucks System.ITupleimplementiert wirdobject.

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

Wenn eine Übereinstimmung eines Eingabewerts mit denUnterpattern des Mustertyps() übereinstimmt, wird eine Methode ausgewählt, indem sie im Typ nach barrierefreien Deklarationen Deconstruct suchen und eine davon auswählen, die dieselben Regeln wie für die Dekonstruktionsdeklaration verwendet. Es ist ein Fehler, wenn ein positional_pattern den Typ ausgelassen, einen einzelnen Unterpattern ohne Bezeichner hat, keine property_subpattern hat und keine simple_designation hat. Dies unterscheidet sich zwischen einer constant_pattern , die Klammer und eine positional_pattern ist. Um die Werte zu extrahieren, die mit den Mustern in der Liste übereinstimmen sollen,

  • Wenn der Typ ausgelassen wird und der Typ des Eingabeausdrucks ein Tupeltyp ist, muss die Anzahl der Unterpattern mit der Kardinalität des Tupels identisch sein. Jedes Tupelelement wird mit dem entsprechenden Unterpattern abgeglichen, und die Übereinstimmung ist erfolgreich, wenn alle diese Elemente erfolgreich sind. Wenn ein Unterpattern über einen Bezeichner verfügt, muss dieser ein Tupelelement an der entsprechenden Position im Tupeltyp benennen.
  • Andernfalls handelt es sich bei einem geeigneten Deconstruct Element des Typs um einen Kompilierungszeitfehler, wenn der Typ des Eingabewerts nicht musterkompatibel mit dem Typ ist. Zur Laufzeit wird der Eingabewert anhand des Typs getestet. Wenn dies fehlschlägt, schlägt die Positionsmustervergleich fehl. Wenn er erfolgreich ist, wird der Eingabewert in diesen Typ konvertiert und Deconstruct mit neu generierten Variablen aufgerufen, um die Ausgabeparameter zu empfangen. Jeder empfangene Wert wird mit dem entsprechenden Unterpattern abgeglichen, und die Übereinstimmung ist erfolgreich, wenn alle diese erfolgreich sind. Wenn ein Unterpattern über einen Bezeichner verfügt, muss dieser einen Parameter an der entsprechenden Position von Deconstruct.
  • Andernfalls wird kein Typ angegeben, und der Eingabewert ist vom Typ object oder einem Typ, der von einer impliziten Verweiskonvertierung in konvertiert System.ITuple werden kann, und kein Bezeichner unter den Unterpattern angezeigt wird, wird die Übereinstimmung verwendet System.ITuple.
  • Andernfalls ist das Muster ein Kompilierungszeitfehler.

Die Reihenfolge, in der Unterpattern zur Laufzeit abgeglichen werden, ist nicht angegeben, und eine fehlgeschlagene Übereinstimmung versucht möglicherweise nicht, alle Unterpattern abzugleichen.

Beispiel: Hier deconieren wir ein Ausdrucksergebnis und stimmen mit den resultierenden Werten mit den entsprechenden geschachtelten Mustern überein:

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

Endbeispiel

Beispiel: Die Namen von Tupelelementen und Deconstruct-Parametern können wie folgt in einem Positionsmuster verwendet werden:

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

Die erzeugte Ausgabe ist

Sum of [10 20 30] is 60

Endbeispiel

11.2.6 Eigenschaftenmuster

Ein property_pattern überprüft, ob der Eingabewert nicht nullist, und stimmt rekursiv mit Werten überein, die durch die Verwendung von barrierefreien Eigenschaften oder Feldern extrahiert wurden.

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

Es ist ein Fehler, wenn ein Unterpattern eines property_pattern keinen Bezeichner enthält.

Es handelt sich um einen Kompilierungszeitfehler, wenn der Typ ein Nullwerttyp (§8.3.12) oder ein nullabler Bezugstyp (§8.9.3) ist.

Hinweis: Ein Nullüberprüfungsmuster fällt aus einem trivialen Eigenschaftsmuster heraus. Um zu überprüfen, ob die Zeichenfolge s ungleich NULL ist, kann eine der folgenden Formulare geschrieben werden:

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

Endnote Angesichts einer Übereinstimmung eines Ausdrucks e mit dem Mustertyp{property_pattern_list}, handelt es sich um einen Kompilierungszeitfehler, wenn der Ausdruck e nicht musterkompatibel mit dem typ T ist, der nach Typ festgelegt ist. Wenn der Typ nicht vorhanden ist, wird der Typ als statischer Typ von e angenommen. Jeder der Bezeichner, die auf der linken Seite seines property_pattern_list angezeigt werden, muss eine lesbare Eigenschaft oder ein lesbares Feld von T festlegen. Wenn die simple_designation des property_pattern vorhanden ist, deklariert sie eine Mustervariable vom Typ T.

Zur Laufzeit wird der Ausdruck auf T getestet. Wenn dies fehlschlägt, schlägt die Übereinstimmung des Eigenschaftenmusters fehl, und das Ergebnis lautet false. Wenn dies erfolgreich ist, wird jedes property_subpattern Feld oder jede Eigenschaft gelesen und ihr Wert mit dem entsprechenden Muster abgeglichen. Das Ergebnis der gesamten Übereinstimmung ist false nur, wenn das Ergebnis einer dieser Übereinstimmungen lautet false. Die Reihenfolge, in der Unterpattern abgeglichen werden, wird nicht angegeben, und eine fehlgeschlagene Übereinstimmung testet möglicherweise nicht alle Unterpattern zur Laufzeit. Wenn die Übereinstimmung erfolgreich ist und die simple_designation der property_pattern ein single_variable_designation ist, wird der deklarierte Wert zugewiesen.

Die property_pattern können verwendet werden, um eine Mustervergleichung mit anonymen Typen zu erzielen.

Beispiel:

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

Endbeispiel

Beispiel: Eine Laufzeittypüberprüfung und eine Variabledeklaration können einem Eigenschaftenmuster wie folgt hinzugefügt werden:

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

Die erzeugte Ausgabe ist

Hello
Hi!
12345
abc

Endbeispiel

11.2.7 Muster verwerfen

Jeder Ausdruck stimmt mit dem Verwerfenmuster überein, was zu dem Wert des Ausdrucks führt, der verworfen wird.

discard_pattern
    : '_'
    ;

Es handelt sich um einen Kompilierungsfehler, um ein Verwerfenmuster in einem relational_expression des Formulars relational_expressionisMusters oder als Muster einer switch_label zu verwenden.

Hinweis: Verwenden Sie in diesen Fällen eine var_pattern mit einem Verwerfen var _, um einem beliebigen Ausdruck zu entsprechen. Endnote

Beispiel:

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

Die erzeugte Ausgabe ist

5.0
0.0
0.0

Hier wird ein Verwerfenmuster verwendet, um einen ganzzahligen Wert zu behandeln null , der nicht über das entsprechende Element der DayOfWeek Enumeration verfügt. Dadurch wird sichergestellt, dass der switch Ausdruck alle möglichen Eingabewerte verarbeitet. Endbeispiel

11.3 Musterunternahme

Bei einer Switch-Anweisung handelt es sich um einen Fehler, wenn das Muster eines Falls von der vorherigen Gruppe nicht überwachter Fälle (§13.8.3) subsumiert wird. Informell bedeutet dies, dass jeder Eingabewert mit einem der vorherigen Fälle abgeglichen worden wäre. Die folgenden Regeln definieren, wann eine Reihe von Mustern ein bestimmtes Muster subsumiert:

Ein Muster P stimmt mit einer Konstante überein, wenn die Spezifikation für das Laufzeitverhalten K dieses Musters mit übereinstimmungtP.K

Eine Reihe von Mustern Qsubsumiert ein Muster P , wenn eine der folgenden Bedingungen enthalten ist:

  • Pist ein konstantes Muster, und jedes der Muster in der Gruppe Q würde mit dem konvertierten Wert übereinstimmenP.
  • Pist ein Var-Muster, und der Satz von Mustern Q ist erschöpfend (§11.4) für den Typ des Mustereingabewerts (§11.1), und entweder ist der Mustereingabewert kein nullabler Typ oder ein Muster Q in übereinstimmungen.null
  • Pist ein Deklarationsmuster mit Typ T und der Satz von Mustern Q ist (T).

11.4 Mustererschöpfend

Informell ist eine Reihe von Mustern für einen Typ erschöpfend, wenn für jeden möglichen Wert dieses Typs außer NULL ein bestimmtes Muster in der Menge anwendbar ist. Die folgenden Regeln definieren, wann eine Reihe von Mustern für einen Typ erschöpfend ist:

Eine Reihe von Mustern Q ist für einen Typ erschöpfendT:

  1. T ist ein integraler oder enumerationstyp oder eine nullable Version einer dieser Typen, und für jeden möglichen Wert des Tnicht nullablen zugrunde liegenden Typs würde ein Muster Q mit diesem Wert übereinstimmen; oder
  2. Bei einigen Mustern handelt es sich um Q ein Var-Muster; oder
  3. Bei einigen Mustern Q handelt es sich um ein Deklarationsmuster für den Typ D, und es gibt eine Identitätskonvertierung, eine implizite Verweiskonvertierung oder eine Boxumwandlung von T zu D.

Beispiel:

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

Endbeispiel