Freigeben über


11 Muster und Musterabgleich

11.1 Allgemein

Ein Muster ist eine syntaktische Form, die mit dem is Operator (§12.12.12) und in einem switch_statement (§13.8.3) verwendet werden kann, um das Shape der Daten auszudrücken, mit denen eingehende Daten verglichen werden sollen. Ein Muster wird auf den Ausdruck einer Switch-Anweisung oder auf eine relational_expression getestet, die sich auf der linken Seite eines is Operators befindet, von denen jeder als Mustereingabewert bezeichnet wird.

11.2 Musterformulare

11.2.1 Allgemein

Ein Muster kann eine der folgenden Formen aufweisen:

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    ;

Ein declaration_pattern und ein var_pattern kö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 .

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, den Wert in einer Variablen dieses Typs bereitstellen.

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : single_variable_designation
    ;
single_variable_designation
    : identifier
    ;

Der Laufzeittyp des Werts wird anhand der im Is-Type-Operator (§12.12.12.12.12.1) angegebenen Regeln im Muster getestet. Wenn der Test erfolgreich ist, entspricht das Muster diesem Wert. Es handelt sich um einen Kompilierungszeitfehler, wenn der Typ ein Nullwerttyp ist (§8.3.12). 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, wenn die simple_designation der Bezeichner _ist, wird ein Verwerfen (§9.2.9.1) und der Wert von e nicht 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.) Wenn simple_designation ein anderer Bezeichner ist, wird eine lokale Variable (§9.2.9) des durch den angegebenen Bezeichner benannten Typs eingeführt. 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 T kompatibel ist, wenn eine Identitätskonvertierung, eine implizite oder explizite Verweiskonvertierung, eine Boxkonvertierung oder eine Unboxing-Konvertierung von E zu T, oder wenn T E es sich um einen geöffneten Typ (§8.4.3) 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

Es handelt sich um einen Fehler, wenn der Typ ein Nullwerttyp ist.

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 T2 Toder 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. 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, entspricht das Muster P dem Wert e, wenn das Ergebnis des Ausdrucks e == v lautettrue; andernfalls
  • das Muster P entspricht 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_pattern entspricht 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
    ;

Bei einem Mustereingabewert (§11.1) e, wenn die Bezeichnung der Bezeichner _ist, wird ein Verwerfen (§9.2.9.1) 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.) Wenn die Bezeichnung ein anderer Bezeichner ist, ist der Wert von e zur Laufzeit 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.

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 K überein, wenn die Spezifikation für das Laufzeitverhalten P dieses Musters mit übereinstimmungtK.

Eine Reihe von Mustern Q subsumiert 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 für den Typ T erschöpfend (§11.4).

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 T erschöpfend, wenn eine der folgenden Bedingungen enthalten ist:

  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