Mustervergleich - die Ausdrücke is and switch sowie die Operatoren and, or, and not in patterns

Verwenden Sie den is Ausdruck, die Switch-Anweisung und den Schalterausdruck , um einem Eingabeausdruck eine beliebige Anzahl von Merkmalen zuzuordnen. C# unterstützt mehrere Muster, einschließlich „Typ“, „Konstante“, „relational“, „Eigenschaft“, „Liste“, „var“ und „Ausschuss“. Sie können Muster kombinieren, indem Sie die booleschen Logikstichwörter and, orund not.

Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie eine erste Dokumentation zu Funktionen in der öffentlichen Vorschau für die kommende Sprachversion.

In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.

Tipp

Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.

Die folgenden C#-Ausdrücke und -Anweisungen unterstützen Musterabgleiche:

In diesen Konstrukten können Sie einen Eingabeausdruck gegen jedes der folgenden Muster abgleichen:

  • Deklarationsmuster: Überprüfen Sie den Laufzeittyp eines Ausdrucks, und weisen Sie, wenn eine Übereinstimmung erfolgreich ist, einer deklarierten Variablen ein Ausdrucksergebnis zu.
  • Typmuster: Überprüfen Sie den Laufzeittyp eines Ausdrucks.
  • Konstantenmuster: Testen, dass ein Ausdrucksergebnis einer angegebenen Konstante entspricht.
  • Relationale Muster: Vergleichen eines Ausdrucksergebnisses mit einer angegebenen Konstante.
  • Logische Muster: Testen, dass ein Ausdruck einer logischen Kombination von Mustern entspricht.
  • Eigenschaftsmuster: Testen, ob die Eigenschaften oder Felder eines Ausdrucks mit verschachtelten Mustern übereinstimmen.
  • Positionelles Muster: Dekonstruieren Sie ein Ausdrucksergebnis und testen Sie, ob die resultierenden Werte verschachtelten Mustern entsprechen.
  • var Muster: Vergleichen Sie einen beliebigen Ausdruck und weisen Sie dessen Ergebnis einer deklarierten Variablen zu.
  • Verwerfungsmuster: Übereinstimmung mit einem beliebigen Ausdruck.
  • Listenmuster: Testen Sie, dass eine Abfolge von Elementen den entsprechenden geschachtelten Mustern entspricht.

Logical, Eigenschaft, positional, and list patterns are recursive patterns. Das heißt, Sie können geschachtelte Muster enthalten.

Ein Beispiel für die Verwendung dieser Muster zum Erstellen eines datengesteuerten Algorithmus finden Sie im Lernprogramm: Verwenden des Musterabgleichs zum Erstellen von typgesteuerten und datengesteuerten Algorithmen.

Deklarations- und Typmuster

Verwenden Sie Deklarations- und Typmuster, um zu überprüfen, ob der Laufzeittyp eines Ausdrucks mit einem bestimmten Typ kompatibel ist. Mithilfe eines Deklarationsmusters können Sie auch eine neue lokale Variable deklarieren. Wenn ein Deklarationsmuster einem Ausdruck entspricht, weist es die Variable dem konvertierten Ausdrucksergebnis zu, wie das folgende Beispiel zeigt:

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

Ein Deklarationsmuster mit dem Typ T entspricht einem Ausdruck, wenn ein Ausdrucksergebnis nicht NULL ist und eine der folgenden Bedingungen zutrifft:

  • Der Laufzeittyp eines Ausdrucksergebnisses hat eine Identitätskonvertierung nach T.
  • Der Typ T ist ein ref struct Typ, und es gibt eine Identitätskonvertierung vom Ausdruck in T.
  • Der Laufzeittyp eines Ausdrucksergebnisses wird vom Typ T abgeleitet, oder er implementiert die T-Schnittstelle, oder es gibt eine andere implizite Verweiskonvertierung von diesem Typ zu T. Diese Bedingung deckt Vererbungsbeziehungen und Schnittstellenimplementierungen ab. Das folgende Beispiel zeigt zwei Fälle, in denen diese Bedingung whr ist:
    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    
    Im vorangehenden Beispiel entspricht das erste Muster im ersten Aufruf der GetSourceLabel-Methode einem Argumentwert, da der Laufzeittyp int[] des Arguments vom Typ Array abgeleitet wird. Im zweiten Aufruf der GetSourceLabel-Methode wird der Laufzeittyp List<T> des Arguments nicht vom Typ Array abgeleitet, er implementiert aber die ICollection<T>-Schnittstelle.
  • Der Laufzeittyp eines Ausdrucksergebnisses ist ein Nullwerte zulassender Wert mit dem zugrunde liegenden Typ T und Nullable<T>.HasValue ist true.
  • A boxing or unboxing Konvertierung existiert vom Laufzeittyp eines Ausdrucksergebnisses zum Typ T , wenn der Ausdruck keine Instanz eines ref struct.

Deklarationsmuster berücksichtigen keine benutzerdefinierten Konvertierungen oder implizite Span-Konvertierungen.

Im folgenden Beispiel werden die beiden letzten Bedingungen veranschaulicht:

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

Um nur den Typ eines Ausdrucks zu überprüfen, verwenden Sie einen Verwerfen _ anstelle des Namens einer Variablen, wie das folgende Beispiel zeigt:

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

Verwenden Sie zu diesem Zweck ein Typmuster, wie das folgende Beispiel zeigt:

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

Wie ein Deklarationsmuster stimmt ein Typmuster mit einem Ausdruck überein, wenn ein Ausdrucksergebnis nicht null ist und sein Laufzeittyp eine der oben genannten Bedingungen erfüllt.

Verwenden Sie ein negiertes Konstantenmusternull, um nach nicht null zu suchen, wie im folgenden Beispiel gezeigt:

if (input is not null)
{
    // ...
}

Weitere Informationen finden Sie in den Abschnitten " Deklarationsmuster " und "Typmuster " der C#-Sprachspezifikation.

Konstantenmuster

Das Konstantenmuster ist eine alternative Syntax, wenn == der rechte Operand eine Konstante ist. Verwenden Sie ein Konstantenmuster , um zu testen, ob ein Ausdrucksergebnis einer angegebenen Konstante entspricht, wie im folgenden Beispiel gezeigt:

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

In einem Konstantenmuster können Sie einen beliebigen konstanten Ausdruck verwenden, z. B.:

Der Ausdruck muss ein Typ sein, der in den Konstantentyp konvertiert werden kann, mit einer Ausnahme: Ein Ausdruck, dessen Typ Span<char> mit Konstantenzeichenfolgen übereinstimmt oder ReadOnlySpan<char> abgeglichen werden kann.

Verwenden Sie ein Konstantenmuster, um auf null zu prüfen, wie im folgenden Beispiel gezeigt:

if (input is null)
{
    return;
}

Der Compiler garantiert, dass ein Benutzerüberladungsgleichheitsoperator == beim Auswerten des Ausdrucks x is nullnicht aufgerufen wird.

Sie können ein negatednull Konstantenmuster verwenden, um auf Nicht-Null zu prüfen, wie das folgende Beispiel zeigt:

if (input is not null)
{
    // ...
}

Weitere Informationen finden Sie im Abschnitt Konstantenmuster des Hinweises zum Featurevorschlag.

Relationale Muster

Verwenden Sie ein relationales Muster , um ein Ausdrucksergebnis mit einer Konstante zu vergleichen, wie das folgende Beispiel zeigt:

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

Verwenden Sie in einem relationalen Muster eine der relationalen Operatoren<, >, , <=oder >=. Der rechte Teil eines relationalen Musters muss ein konstanter Ausdruck sein. Der Konstantenausdruck kann einen der integralen, Gleitkomma-, char- oder enum-Typen haben.

Um zu überprüfen, ob sich ein Ausdrucksergebnis in einem bestimmten Bereich befindet, gleichen Sie es mit einem konjunktiven and-Muster ab, wie im folgenden Beispiel gezeigt:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    >= 3 and < 6 => "spring",
    >= 6 and < 9 => "summer",
    >= 9 and < 12 => "autumn",
    12 or (>= 1 and < 3) => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

Wenn ein Ausdrucksergebnis null in den Typ einer Konstante mithilfe einer Null-Konvertierung oder unboxing-Konvertierung konvertiert werden kann, stimmt ein relationales Muster nicht mit dem Ausdruck überein.

Weitere Informationen finden Sie im Abschnitt "Relationale Muster " der C#-Sprachspezifikation.

Logische Muster

Verwenden Sie die notKombinationszeichen und andor Musterkombinationen, um die folgenden logischen Muster zu erstellen:

  • Negationnot , das mit einem Ausdruck übereinstimmt, wenn das negierte Muster nicht mit dem Ausdruck übereinstimmt. Das folgende Beispiel zeigt, wie Sie ein constantnull Muster negieren können, um zu prüfen, ob ein Ausdruck nicht null ist:

    if (input is not null)
    {
        // ...
    }
    
  • Conjunctiveand Muster, das auf einen Ausdruck passt, wenn beide Muster auf den Ausdruck passen. Im folgenden Beispiel wird gezeigt, wie Sie relationale Muster kombinieren können, um zu überprüfen, ob ein Wert in einem bestimmten Bereich liegt:

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    static string Classify(double measurement) => measurement switch
    {
        < -40.0 => "Too low",
        >= -40.0 and < 0 => "Low",
        >= 0 and < 10.0 => "Acceptable",
        >= 10.0 and < 20.0 => "High",
        >= 20.0 => "Too high",
        double.NaN => "Unknown",
    };
    
  • Disjunctiveor Muster, das auf einen Ausdruck passt, wenn eines der beiden Muster auf den Ausdruck passt, wie das folgende Beispiel zeigt:

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    static string GetCalendarSeason(DateTime date) => date.Month switch
    {
        3 or 4 or 5 => "spring",
        6 or 7 or 8 => "summer",
        9 or 10 or 11 => "autumn",
        12 or 1 or 2 => "winter",
        _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
    };
    

Wie im vorherigen Beispiel gezeigt, können Sie die Musterkombinatoren wiederholt in einem Muster verwenden.

Vorrang und Auswertungsreihenfolge

Die Musterkombinatoren überprüfen Ausdrücke in dieser Reihenfolge, basierend auf der Bindungsreihenfolge von Ausdrücken:

  • not
  • and
  • or

The not -Muster bindet sich zuerst an seinen Operanden. The and -Muster bindet sich nach jedem not -Musterausdruck. Das or Muster bindet nach allen not Mustern, and die an Operanden gebunden sind. Im folgenden Beispiel wird versucht, alle Zeichen abzugleichen, die nicht kleingeschrieben sind, a bis z. Es liegt ein Fehler vor, denn die not -Muster vor dem and pattern:

// Incorrect pattern. `not` binds before `and`
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';

Die Standardbindung bedeutet, dass das vorherige Beispiel als das folgende Beispiel geparst wird:

// The default binding without parentheses is shows in this method. `not` binds before `and`
static bool IsNotLowerCaseLetterDefaultBinding(char c) => c is ((not >= 'a') and <= 'z');

Um den Fehler zu beheben, geben Sie an, dass das not Muster an den >= 'a' and <= 'z' Ausdruck gebunden werden soll:

// Correct pattern. Force `and` before `not`
static bool IsNotLowerCaseLetterParentheses(char c) => c is not (>= 'a' and <= 'z');

Das Hinzufügen von Klammern wird umso wichtiger, je komplizierter Ihre Muster werden. Verwenden Sie im Allgemeinen Klammern, um Ihre Muster für andere Entwickler zu verdeutlichen, wie das folgende Beispiel zeigt:

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

Hinweis

Die Reihenfolge, in der der Compiler Muster überprüft, die dieselbe Bindungsreihenfolge aufweisen, ist nicht definiert. Zur Laufzeit kann der Compiler zuerst die rechts geschachtelten Muster mehrerer or Muster und mehrerer and Muster überprüfen.

Weitere Informationen finden Sie im Abschnitt "Musterkombinatoren " der C#-Sprachspezifikation.

Eigenschaftsmuster

Verwenden Sie ein Eigenschaftenmuster , um die Eigenschaften oder Felder eines Ausdrucks mit geschachtelten Mustern abzugleichen, wie das folgende Beispiel zeigt:

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

Ein Eigenschaftenmuster entspricht einem Ausdruck, wenn das Ausdrucksergebnis ungleich NULL ist und jedes geschachtelte Muster der entsprechenden Eigenschaft oder des Felds des Ausdrucksergebnisses entspricht.

Sie können eine Laufzeittypüberprüfung und eine Variabledeklaration zu einem Eigenschaftenmuster hinzufügen, wie das folgende Beispiel zeigt:

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

Dieses Konstrukt bedeutet insbesondere, dass das leere Eigenschaftenmuster is { } mit allem nicht null übereinstimmt, und Sie können es anstelle einer is not null Variablen verwenden: somethingPossiblyNull is { } somethingDefinitelyNotNull.

if (GetSomeNullableStringValue() is { } nonNullValue) // Empty property pattern with variable creation
{
    Console.WriteLine("NotNull:" + nonNullValue);
}
else
{
    nonNullValue = "NullFallback"; // we can access the variable here.
    Console.WriteLine("it was null, here's the fallback: " + nonNullValue);
}

Ein Eigenschaftsmuster ist ein rekursives Muster. Sie können ein beliebiges Muster als geschachteltes Muster verwenden. Verwenden Sie ein Eigenschaftsmuster, um Teile von Daten gegen geschachtelte Muster abzugleichen, wie im folgenden Beispiel gezeigt:

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

Im vorherigen Beispiel werden der orMusterkombinator und Datensatztypen verwendet.

Sie können auf geschachtelte Eigenschaften oder Felder innerhalb eines Eigenschaftsmusters verweisen. Diese Funktion wird als erweitertes Eigenschaftenmuster bezeichnet. Beispielsweise können Sie die Methode aus dem vorherigen Beispiel in den folgenden äquivalenten Code umgestalten:

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

Weitere Informationen finden Sie im Abschnitt "Eigenschaftenmuster " des C#-Standards.

Tipp

Um die Lesbarkeit von Code zu verbessern, verwenden Sie die Formatvorlagenregel "Vereinfachen" (IDE0170). Es schlägt Orte vor, um erweiterte Eigenschaftsmuster zu verwenden.

Positionsmuster

Verwenden Sie ein Positionsmuster , um einen Ausdruck zu deconieren und die resultierenden Werte mit den entsprechenden geschachtelten Mustern abzugleichen, wie das folgende Beispiel zeigt:

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

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

Im vorherigen Beispiel enthält der Typ eines Ausdrucks die Deconstruct-Methode , die das Muster verwendet, um ein Ausdrucksergebnis zu deconieren.

Wichtig

Die Reihenfolge der Elemente in einem Positionsmuster muss mit der Reihenfolge der Parameter in der Deconstruct-Methode übereinstimmen. Der für das Positionsmuster generierte Code ruft die Deconstruct Methode auf.

Sie können auch Ausdrücke von Tupeltypen gegen Positionsmuster abgleichen. Mit diesem Ansatz können Sie mehrere Eingaben mit verschiedenen Mustern abgleichen, wie im folgenden Beispiel gezeigt:

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

Im vorherigen Beispiel werden relationale und logische Muster verwendet.

Sie können die Namen von Tupelelementen und Deconstruct-Parametern in einem Positionsmuster verwenden, wie im folgenden Beispiel gezeigt:

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

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

Sie können ein Positionsmuster auch auf eine der folgenden Arten erweitern:

  • Fügen Sie eine Laufzeittypüberprüfung und eine Variablendeklaration hinzu, wie im folgenden Beispiel gezeigt:

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    Im vorherigen Beispiel werden Datensätze mit Feldern fester Breite verwendet, die implizit die Deconstruct-Methode bereitstellen.

  • Verwenden Sie ein Eigenschaftsmuster in einem Positionsmuster, wie im folgenden Beispiel gezeigt:

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • Kombinieren Sie die beiden vorherigen Verwendungen, wie das folgende Beispiel zeigt:

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

Ein Positionsmuster ist ein rekursives Muster. Das heißt, Sie können ein beliebiges Muster als ein geschachteltes Muster verwenden.

Weitere Informationen finden Sie im Abschnitt Positionsmuster des Hinweises zum Featurevorschlag.

var-Muster

Verwenden Sie ein var Muster , um einem beliebigen Ausdruck zuzuordnen, einschließlich null, und weisen Sie dessen Ergebnis einer neuen lokalen Variablen zu, wie das folgende Beispiel zeigt:

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

Ein var-Muster ist nützlich, wenn Sie eine temporäre Variable in einem booleschen Ausdruck benötigen, um das Ergebnis von Zwischenberechnungen zu speichern. Sie können ein var-Muster auch verwenden, wenn Sie weitere Überprüfungen in den when-Ausdrücken eines switch-Ausdrucks oder einer switch-Anweisung ausführen müssen, wie im folgenden Beispiel gezeigt:

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

Im vorangegangenen Beispiel ist das Muster var (x, y) gleich einem Positionsmuster(var x, var y).

In einem var Muster ist der Typ einer deklarierten Variablen der Kompilierungszeittyp des Ausdrucks, dem das Muster entspricht.

Weitere Informationen finden Sie im Abschnitt Var-Muster des Hinweises zum Featurevorschlag.

Ausschussmuster

Verwenden Sie ein Verwerfenmuster_ , um einem beliebigen Ausdruck zu entsprechen, einschließlich nulldes folgenden Beispiels:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

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

Im vorherigen Beispiel werden ein Verwerfenmuster und ein ganzzahliger Wert, der nicht über das entsprechende Element der null Enumeration verfügt, behandeltDayOfWeek. Dadurch wird sichergestellt, dass ein switch Ausdruck im Beispiel alle möglichen Eingabewerte behandelt. Wenn Sie kein Ausschussmuster in einem switch-Ausdruck verwenden und keines der Muster des Ausdrucks mit einer Eingabe übereinstimmt, löst die Runtime eine Ausnahme aus. Der Compiler generiert eine Warnung, wenn ein switch-Ausdruck nicht alle möglichen Eingabewerte verarbeiten kann.

Ein Ausschussmuster kann kein Muster in einem is-Ausdruck oder in einer switch-Anweisung sein. Verwenden Sie in diesen Fällen, um einen Ausdruck abzugleichen, ein var-Muster mit einem Ausschussmuster: var _. Ein Ausschussmuster kann kein Muster in einem switch-Ausdruck sein.

Weitere Informationen finden Sie im Abschnitt Ausschussmuster des Hinweises zum Featurevorschlag.

Muster in Klammern

Sie können Klammern um ein beliebiges Muster setzen. In der Regel legen Sie dies fest, um die Rangfolge in logischen Mustern hervorzuheben oder zu ändern, wie im folgenden Beispiel gezeigt:

if (input is not (float or double))
{
    return;
}

Listenmuster

Sie können ein Array oder eine Liste mit einer Abfolge von Mustern abgleichen, wie das folgende Beispiel zeigt:

int[] numbers = { 1, 2, 3 };

Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True

Wie im vorherigen Beispiel gezeigt, stimmt ein Listenmuster überein, wenn jedes geschachtelte Muster mit dem entsprechenden Element einer Eingabesequenz übereinstimmt. Sie können ein beliebiges Muster in einem Listenmuster verwenden. Wenn Sie mit einem element übereinstimmen möchten, verwenden Sie das Verwerfenmuster , oder verwenden Sie, wenn Sie das Element auch erfassen möchten, das Var-Muster, wie im folgenden Beispiel gezeigt:

List<int> numbers = new() { 1, 2, 3 };

if (numbers is [var first, _, _])
{
    Console.WriteLine($"The first element of a three-item list is {first}.");
}
// Output:
// The first element of a three-item list is 1.

Die vorherigen Beispiele entsprechen einer ganzen Eingabesequenz mit einem Listenmuster. Um Elemente nur am Anfang oder Am Ende einer Eingabesequenz abzugleichen, verwenden Sie das Segmentmuster.., wie das folgende Beispiel zeigt:

Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // False

Console.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // True

Console.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False

Ein Slicemuster entspricht 0 (null) oder mehr Elementen. Sie können höchstens ein Slicemuster in einem Listenmuster verwenden. Das Slicemuster kann nur in einem Listenmuster angezeigt werden.

Sie können auch einen Teilmuster wie im folgenden Beispiel gezeigt in einem Segmentmuster verschachteln:

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

Weitere Informationen finden Sie unter Listenmuster in der C#-Sprachspezifikation.

Geschlossene Hierarchiemuster

Ab C# 15 ist ein switch Ausdruck, dessen Steuerungstyp eine closed Klasse ist , erschöpfend , wenn die Arme alle direkten Untergeordneten dieser Klasse behandeln. Der Compiler erfordert keinen Standardarm, da der Switch erschöpfend ist:

public closed record class PaymentMethod;
public record class Cash : PaymentMethod;
public record class Card(string Last4) : PaymentMethod;
public record class BankTransfer(string Iban) : PaymentMethod;
public static string Describe(PaymentMethod method) => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: every direct descendant of 'PaymentMethod' is handled.
};

Ein geschlossener Hierarchieschalter ist nur erschöpfend, wenn jeder direkte Nachfolger von der Position des Schalters aus erreichbar ist. Wenn ein direkter Nachfolger weniger zugänglich ist als der geschlossene Basistyp und nicht auf dem Switch-Standort sichtbar ist, behandelt der Compiler ihn als unbehandelt und warnt, dass der Switch nicht erschöpfend ist.

Eine geschlossene public Basisklasse kann z. B. über einen internal direkten Nachfolger verfügen. Code in derselben Assembly sieht den vollständigen Satz von Nachfolgern, code in einer anderen Assembly jedoch nicht:

// Assembly 1
public closed record class Shape;
public record class Circle(double Radius) : Shape;
internal record class Triangle(double Base, double Height) : Shape;
// Same assembly: 'Triangle' is visible, so the switch is exhaustive.
internal static double Area(Shape shape) => shape switch
{
    Circle(var r) => Math.PI * r * r,
    Triangle(var b, var h) => 0.5 * b * h,
};
// Assembly 2
public static string Name(Shape shape) => shape switch
{
    Circle => "circle",
    // Warning CS8509: the switch expression doesn't handle all possible values.
    // 'Triangle' is a direct descendant of 'Shape' but isn't visible here.
};

Um die Erschöpfendheit in Assembly 2 wiederherzustellen, fügen Sie einen Verwerfen-Arm (_ => ...) hinzu, fügen Sie einen Basisklassenarm (Shape im vorherigen Beispiel) hinzu, oder machen Sie alle direkten untergeordneten Elemente mindestens so zugänglich wie der geschlossene Basistyp.

Wenn der Steuerungstyp nullfähig ist, ist ein zusätzlicher Wert, null den der Switch verarbeiten muss. Eine Umstellung PaymentMethod? , die einen null Arm ausgelassen, ist nicht erschöpfend, auch wenn jeder direkte Nachfolger abgeglichen wird.

Die Ableitung von einer geschlossenen Klasse ist nicht transitiv: Ein nicht geschlossener Nachfolger einer geschlossenen Klasse kann aus anderen Assemblys abgeleitet werden. Der Compiler behandelt nur die direkten Nachfolger als erschöpfenden Satz. Wenn Sie einen Wechsel über einen Absteigenden vornehmen möchten, profitieren Sie auch von der Erschöpfendheitsprüfung, deklarieren Sie den Nachfolger closed (oder sealed).

Da indirekte Nachfolger den vollständigen Satz der geschlossenen Basis nicht erweitern, müssen Sie keinen Arm für jeden transitiven Untertyp hinzufügen, um erschöpfend zu sein. Die Subsumtion zwischen den Armen funktioniert immer noch auf die gleiche Weise wie für jede Klassenhierarchie: Ein Arm, der einem Basistyp entspricht, deckt jeden Untertyp ab, und ein späterer Arm, der einem dieser Untertypen entspricht, ist nicht erreichbar. Erwägen Sie eine geschlossene, Vehicle deren direkte Nachfolger und Truckein indirekter Nachfolger Sedan in einer anderen Assembly deklariert sindCar:

// Assembly 1
public closed record class Vehicle;
public record class Car(int Doors) : Vehicle;
public record class Truck(double PayloadTons) : Vehicle;

// Assembly 2
public record class Sedan(int Doors) : Car(Doors);

Eine Umstellung Vehicle ist erschöpfend, nachdem sie behandelt Car und Truck, auch wenn Sedan vorhanden. Der Car Arm deckt jeden Sedan Wert ab:

public static string Category(Vehicle v) => v switch
{
    Car => "car",
    Truck => "truck",
    // No warning. The 'Car' arm covers 'Sedan' through ordinary subtype matching.
};

Platzieren Sie den Arm vor dem Car Arm, um sie gezielt zu Sedan verteilen. Der Car Arm bleibt erreichbar, da er immer noch mit jedem Car übereinstimmt, der kein Sedan:

public static string CategorySedanFirst(Vehicle v) => v switch
{
    Sedan => "sedan",
    Car => "car",      // Reachable: 'Car' values that aren't 'Sedan'.
    Truck => "truck",
};

Das Umkehren dieser beiden Arme führt zu einem Subsummierungsfehler, genau wie in jeder anderen Klassenhierarchie. Der Compiler erkennt, dass der Car Arm bereits deckt Sedan:

string Category(Vehicle v) => v switch
{
    Car => "car",
    Sedan => "sedan",  // Error CS8510: the pattern is unreachable. It has already been handled by 'Car => ...'.
    Truck => "truck",
};

Wenn Sie erschöpfen möchten, um die Hierarchie weiter unten zu verfolgen, deklarieren Sie Car sich selbst closed. Der Compiler behandelt dann alle direkten Untergeordneten von Car (z Sedan. B. ) als erschöpfenden Satz, der in Car. Eine Option, deren Verwaltungstyp erschöpfend ist, wenn eine der folgenden Aktionen ausgeführt wird Car :

  • Behandelt jeden direkten Untergeordneten mit Car seinem eigenen Arm.
  • Enthält einen Car Arm, der jeden Car Wert abdeckt (einschließlich aller Untertypen).
  • Enthält einen Verwerfen-Arm (_ => ...) oder einen Arm für einen Basistyp von Car, z Vehicle. B. . .

Ein Switch, dessen Steuerungstyp auswahlmöglichkeiten hat Vehicle : Code, der behandelt Car und Truck noch erschöpfend ist, da der Car Arm jeden Untertyp von Car. Die Markierung Carclosed bietet Ihnen einfach eine zweite Option für diesen Schalter. Sie können den einzelnen Car Arm beibehalten oder durch einen Arm pro direkter Untergeordneter Car (neben dem Truck Arm) ersetzen und trotzdem erschöpfend sein.

Die Markierung Carclosed macht sie auch implizit abstract, was bedeutet, dass Sie keine Instanzen Car von direkt mehr erstellen können. Das passt möglicherweise nicht zu Ihrem Design. Wenn Sie instanziierbar bleiben müssen Car , lassen Sie es offen und versenden Sie sie an die spezifischen Untertypen, die Sie interessieren, indem Sie Arme bestellen, wie zuvor gezeigt.

Typparameter für Typen

Ein switch Ausdruck, dessen Regeltyp ein Typparameter ist, der auf eine geschlossene Klasse beschränkt ist, ist auf denselben Ausdrücken wie ein Wechsel über die geschlossene Klasse selbst erschöpfend. Um eine geschlossene Hierarchie aus generischem Code zu verteilen, beschränken Sie den Typparameter auf die geschlossene Basis, und behandeln Sie alle direkten untergeordneten Elemente:

public static string Describe<X>(X method) where X : PaymentMethod => method switch
{
    Cash => "cash",
    Card(var last4) => $"card ending {last4}",
    BankTransfer(var iban) => $"bank transfer to {iban}",
    // No warning: 'X' is constrained to the closed type 'PaymentMethod',
    // so handling every direct descendant exhausts the switch.
};

Weitere Informationen finden Sie im geschlossenen Modifizierer. Informationen zur Spezifikation finden Sie unter "Geschlossene Hierarchien".

Union-Muster

Beginnend mit C# 15, wenn der eingehende Wert eines Musters ein Union-Typ ist, lösen Muster automatisch die Union aus. Sie gelten für das Eigentum der Union Value und nicht für den Unionswert selbst. Dieses Verhalten macht die Union transparent zum Musterabgleich:

public record class Cat(string Name);
public record class Dog(string Name);
public union Pet(Cat, Dog);

string Describe(Pet pet) => pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
};

Zwei Muster sind Ausnahmen: das var Muster und das Verwerfenmuster _ gelten für den Unionswert selbst, nicht seine Value Eigenschaft.

Das null Muster überprüft, ob die Union Value null ist. Bei klassenbasierten Gewerkschaften ist es auch erfolgreich, null wenn der Unionsbezug selbst null ist.

Wenn ein Union-Typ das Nicht-Boxing-Zugriffsmuster (HasValue und TryGetValue -member) bereitstellt, verwendet der Compiler diese Member, um Box-Wert-Typ-Fälle während des Musterabgleichs zu vermeiden.

Weitere Informationen finden Sie unter Union-Abgleich. Die Spezifikation finden Sie unter "Unions".

C#-Sprachspezifikation

Weitere Informationen finden Sie im Abschnitt "Muster und Musterabgleich " der C#-Sprachspezifikation, einschließlich:

Weitere Informationen