Pattern Matching prüft einen Wert gegen eine Form: „ist es ein Circle?“, „ist es null?“, „ist sein Status "paid" und sein Total über 100?“. Gelingt der Test, kann das Muster außerdem Teile des Werts in neue Variablen herausziehen. Es begann klein in C# 7 mit dem Operator is und ist seitdem mit jeder Version gewachsen, deshalb nennt diese Seite bei jeder Form die Version.
Das is-Typmuster (C# 7)
Vor C# 7 brauchte es zwei Schritte, einen Typ zu prüfen und zu verwenden: as plus Null-Prüfung oder is plus Cast. Das Typmuster erledigt beides auf einmal und gibt dir eine typisierte Variable:
Ausgabe:
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card ist nur wahr, wenn p ein CardPayment ist, das nicht null ist (oder eine davon abgeleitete Klasse), und dann enthält card es mit dem richtigen statischen Typ. Ein null-Wert passt nie auf ein Typmuster, deshalb fällt die null-Zahlung bis zur Null-Prüfung durch.
Gültigkeitsbereich und definitive Zuweisung
Die Mustervariable existiert im umgebenden Block, aber der Compiler lässt dich sie nur dort lesen, wo der Abgleich bekanntermaßen gelungen ist. So funktioniert die Form mit der Guard Clause, bei der du früh aussteigst und die Variable danach verwendest:
Ausgabe:
19.99
42 is over 40
price must be text
boxed is int n && n > 40 zeigt die andere verbreitete Form: Die Variable ist rechts von && verwendbar, weil diese Seite nur läuft, wenn der Abgleich gelungen ist. Rechts von || ist sie nicht zugewiesen, und der Compiler sagt das (CS0165).
Muster entboxen auch: boxed is int n gelingt bei einem geboxten int und gibt dir den Wert ohne einen Cast, der werfen könnte. Bei Nullable-Werttypen gelingt maybe is int n genau dann, wenn maybe einen Wert hat.
Konstanten- und Null-Muster
Auch eine Konstante ist ein Muster. x is null ist das meistverwendete, und C# 7 erlaubt auch x is 0, status is "paid" oder einen Enum-Member:
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
Viele Teams bevorzugen is null gegenüber == null, weil es sich nicht durch eine Operatorüberladung am Typ umleiten lässt. C# 9 fügt die verneinte Form hinzu, is not null.
Muster in switch-Anweisungen (C# 7)
Dieselben Typmuster können cases beschriften, und eine when-Klausel ergänzt eine Bedingung. Die Fälle werden von oben nach unten geprüft, ordne sie also vom Spezifischen zum Allgemeinen:
static string Describe(object o)
{
switch (o)
{
case null: return "nothing";
case int n when n < 0: return "negative number";
case int n: return $"number {n}";
case string s when s.Length == 0: return "empty text";
case string s: return $"text of {s.Length} chars";
case IEnumerable<int> list: return "a sequence of numbers";
default: return o.GetType().Name;
}
}
Describe(-4); // negative number
Describe(""); // empty text
Describe(new List<int>()); // a sequence of numbers
Describe(2.5); // Double
Der Compiler prüft die Reihenfolge: Ein Fall, der nie passen kann, weil ein früherer ihn abdeckt, ist Fehler CS8120. default wird immer zuletzt ausgewertet, wo auch immer es steht. Dieselbe Logik als if/is-Ketten geschrieben, wie im Zahlungsbeispiel oben, funktioniert in jeder C#-Version ab 7.
switch-Ausdrücke (C# 8)
Ein switch-Ausdruck ist die kompakte Form für „einen Wert aus mehreren Fällen berechnen“. Zuerst kommt der Wert, dann switch, dann Arme der Form pattern => result, getrennt durch Kommas, mit _ als Auffangmuster:
public abstract record Shape;
public record Circle(double Radius) : Shape;
public record Rectangle(double Width, double Height) : Shape;
static double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle { Width: var w, Height: var h } => w * h,
_ => throw new ArgumentException("Unknown shape", nameof(shape)),
};
Area(new Rectangle(3, 4)); // 12
Passt zur Laufzeit kein Arm, wirft er SwitchExpressionException, und der Compiler warnt (CS8509), wenn er sieht, dass eine Eingabe nicht behandelt wird. Die Seite zum switch-Ausdruck behandelt die Syntax für sich; der Rest dieser Seite dreht sich um die Muster, die du in die Arme setzen kannst.
Property-Muster (C# 8, erweitert in C# 10)
Ein Property-Muster prüft die Properties eines Objekts gegen verschachtelte Muster in geschweiften Klammern. Das Objekt darf außerdem nicht null sein, { } allein bedeutet also „nicht null“:
public record Address(string City, string Country);
public record Order(decimal Total, string Status, Address ShipTo, int Items);
static string Shipping(Order order) => order switch
{
{ Status: "cancelled" } => "no shipment",
{ ShipTo.Country: "PT", Total: >= 50m } => "free, domestic", // C# 10 dotted form
{ ShipTo.Country: "PT" } => "4.90, domestic",
{ Total: > 200m } => "free, international",
_ => "12.00, international",
};
Shipping(new Order(60m, "paid", new Address("Porto", "PT"), 2)); // free, domestic
Shipping(new Order(260m, "paid", new Address("Lyon", "FR"), 2)); // free, international
In C# 8 und 9 ist die verschachtelte Form Pflicht: { ShipTo: { Country: "PT" } }. C# 10 erlaubt die oben verwendete Kurzform mit Punkt. Property-Muster funktionieren auch mit is, wodurch sich Prüfungen mit mehreren Bedingungen wie eine Beschreibung lesen: if (order is { Status: "paid", Items: > 0 }).
Relationale und logische Muster (C# 9)
C# 9 hat Vergleiche (<, <=, >, >=) und die Kombinatoren and, or und not hinzugefügt, die aus Bereichen Muster machen:
static string Grade(int score) => score switch
{
< 0 or > 100 => "invalid",
>= 90 => "A",
>= 75 and < 90 => "B",
>= 50 => "C",
_ => "F",
};
Grade(82); // B
Grade(101); // invalid
if (input is not null and not "") { ... }
if (c is >= 'a' and <= 'z' or >= 'A' and <= 'Z') { ... } // and binds tighter than or
Die Entsprechung in C# 7 ist eine if/else if-Kette oder ein switch mit when-Klauseln (case int s when s >= 90:).
Tupel- und positionelle Muster (C# 8)
Ein switch über mehrere Werte gleichzeitig funktioniert über ein Tupel:
static string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(> 0, > 0) => "I",
(< 0, > 0) => "II",
(< 0, < 0) => "III",
(> 0, < 0) => "IV",
_ => "on an axis",
};
Quadrant(-2, 5); // II
Ein positionelles Muster tut dasselbe für jeden Typ mit einer Methode Deconstruct, Records eingeschlossen: case Point(0, 0): oder p is Rectangle(var w, var h) && w == h.
Listenmuster (C# 11)
Listenmuster prüfen Arrays und Listen anhand ihrer Elemente. .. passt auf beliebig viele Elemente (ein „Slice“), und jede Elementposition kann ein beliebiges anderes Muster enthalten:
static string Route(string[] args) => args switch
{
[] => "help",
["add", var item] => $"add {item}",
["remove", var item, ..] => $"remove {item}",
[var cmd, ..] => $"unknown command {cmd}",
};
Route(new[] { "add", "milk" }); // add milk
Route(new[] { "remove", "milk", "now" }); // remove milk
int[] nums = { 1, 2, 3, 4 };
if (nums is [1, .., var last]) Console.WriteLine(last); // 4
Welche Version was eingeführt hat
| Muster | Beispiel | Version |
|---|---|---|
| Typmuster mit Variable | o is Circle c | C# 7.0 |
| Konstantenmuster | x is null, n is 0 | C# 7.0 |
var-Muster | x is var v | C# 7.0 |
Muster in case-Labels, when-Guards | case int n when n < 0: | C# 7.0 |
switch-Ausdruck, Discard _ | x switch { ... } | C# 8 |
| Property-Muster | { Status: "paid" } | C# 8 |
| Tupelmuster | (x, y) switch { (0, 0) => ... } | C# 8 |
| Positionelles Muster | Point(0, 0) | C# 8 |
| Relationales Muster | > 100, <= 0 | C# 9 |
| Logische Muster | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| Nacktes Typmuster | case Circle:, Circle => ... | C# 9 |
| Erweitertes Property-Muster | { ShipTo.City: "Porto" } | C# 10 |
| Listenmuster und Slice | [first, .., last] | C# 11 |
Häufige Fehler
- Fälle vom Allgemeinen zum Spezifischen ordnen.
case Payment p:vorcase CardPayment c:macht den zweiten unerreichbar (CS8120). Spezifisches zuerst. - Erwarten, dass ein Typmuster auf null passt.
null is string sist false. Behandlenullmit einem eigenen Fall oderis null. - Die Mustervariable dort lesen, wo der Abgleich gescheitert sein könnte. Nach
a is int n || ...istnnicht zugewiesen (CS0165). - Lange switch-Blöcke über Typen statt virtueller Methoden. Wenn jede neue Unterklasse bedeutet, denselben switch an fünf Stellen zu bearbeiten, gehört das Verhalten in die Klassenhierarchie. Muster glänzen bei Daten von außen (JSON-Formen, Nachrichten, Tupel von Eingaben) und bei geschlossenen Mengen von Typen, die du kontrollierst.
- Das Auffangmuster in einem switch-Ausdruck vergessen. Eine nicht passende Eingabe wirft zur Laufzeit; lies die Warnung CS8509.
Häufig gestellte Fragen
Was ist Pattern Matching in C#?
Pattern Matching prüft einen Wert gegen eine Form und kann, wenn er passt, im selben Schritt Teile davon in Variablen herausziehen. if (shape is Circle c) prüft den Typ und gibt dir ein typisiertes c; order is { Status: "paid", Total: > 100 } prüft Properties. Muster kommen in is-Ausdrücken, switch-Anweisungen und switch-Ausdrücken vor.
Wie verwende ich is mit einer Variable in C#?
if (obj is Customer c) { ... } prüft, ob obj ein Customer ist (nicht null), und weist ihn innerhalb des if an c zu, typisiert als Customer. Das ersetzt das ältere Paar var c = obj as Customer; if (c != null). Die Variable ist nur dort definitiv zugewiesen, wo der Test bekanntermaßen wahr ist, if (!(obj is Customer c)) return; lässt c also nach dem if verwendbar.
Was ist der Unterschied zwischen is null und == null in C#?
x is null prüft immer die Referenz selbst. x == null ruft den ==-Operator des Typs auf, falls er einen überlädt, was meist dieselbe Antwort liefert, aber nicht garantiert. Für das Gegenteil schreibe x != null, !(x is null) oder ab C# 9 x is not null.
Wie funktioniert switch-Pattern-Matching mit when in C#?
Ein case-Label kann ein Muster plus einen when-Guard enthalten: case Order o when o.Total > 100:. Der Fall passt nur, wenn das Muster passt und die Bedingung wahr ist, und die Fälle werden von oben nach unten geprüft, setze die spezifischeren also zuerst. Der Compiler meldet CS8120, wenn ein Fall nie erreicht werden kann, weil ein früherer ihn schon abdeckt.
Welche C#-Version hat welche Muster eingeführt?
C# 7.0: Typ-, Konstanten- und var-Muster in is und case, dazu when. C# 8: switch-Ausdrücke, Property-, Tupel- und positionelle Muster. C# 9: relationale (> 5), logische (and, or, not) und nackte Typmuster. C# 10: erweiterte Property-Muster ({ Address.City: "Porto" }). C# 11: Listenmuster ([1, .., var last]).