Dopasowywanie wzorców (pattern matching) sprawdza, czy wartość ma określony kształt: "czy to Circle?", "czy to null?", "czy jej Status to "paid", a Total przekracza 100?". Gdy test się powiedzie, wzorzec może też wyciągnąć części wartości do nowych zmiennych. Zaczęło się skromnie w C# 7 od operatora is i rozrastało w każdej kolejnej wersji, dlatego ta strona podaje wersję każdej formy.
Wzorzec typu z is (C# 7)
Przed C# 7 sprawdzenie typu i użycie go wymagało dwóch kroków: as i sprawdzenia null albo is i rzutowania. Wzorzec typu robi obie rzeczy naraz i daje ci otypowaną zmienną:
Wynik:
card ending 4242: 49.90
transfer from PT50...: 1200
no payment
other payment: 5
p is CardPayment card jest prawdziwe tylko wtedy, gdy p to niepusty CardPayment (albo klasa z niego pochodna), a wtedy card przechowuje go z właściwym typem statycznym. Wartość null nigdy nie pasuje do wzorca typu, dlatego płatność null przechodzi dalej do sprawdzenia null.
Zasięg i pewne przypisanie
Zmienna wzorca istnieje w otaczającym bloku, ale kompilator pozwala ją odczytać tylko tam, gdzie wiadomo, że dopasowanie się powiodło. Dzięki temu działa klauzula ochronna (guard clause), w której wychodzisz wcześniej, a zmiennej używasz później:
Wynik:
19.99
42 is over 40
price must be text
boxed is int n && n > 40 pokazuje drugi częsty kształt: zmiennej można używać po prawej stronie &&, bo ta strona wykonuje się tylko po udanym dopasowaniu. Po prawej stronie || nie jest przypisana i kompilator to zgłasza (CS0165).
Wzorce też rozpakowują (unboxing): boxed is int n udaje się dla opakowanego int i daje wartość bez rzutowania, które mogłoby rzucić wyjątek. Dla typów wartościowych nullable maybe is int n udaje się dokładnie wtedy, gdy maybe ma wartość.
Wzorce stałej i null
Stała to również wzorzec. x is null to najczęściej używany, a C# 7 pozwala też na x is 0, status is "paid" albo składową enuma:
if (customer is null) return; // null check that ignores any == overload
if (count is 0) Console.WriteLine("empty");
if (level is LogLevel.Error) Alert();
Wiele zespołów woli is null od == null, bo nie może go przekierować przeciążenie operatora w danym typie. C# 9 dodaje formę zaprzeczoną, is not null.
Wzorce w instrukcjach switch (C# 7)
Tymi samymi wzorcami typu można oznaczać przypadki case, a klauzula when dodaje warunek. Przypadki są sprawdzane od góry do dołu, więc ustaw je od szczegółowych do ogólnych:
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
Kompilator sprawdza kolejność: przypadek, który nigdy nie może pasować, bo obejmuje go wcześniejszy, to błąd CS8120. default jest zawsze sprawdzane na końcu, gdziekolwiek stoi. Ta sama logika zapisana łańcuchami if/is, jak w przykładzie z płatnościami powyżej, działa w każdej wersji C# od 7 wzwyż.
Wyrażenia switch (C# 8)
Wyrażenie switch to zwięzła forma dla "oblicz jedną wartość z kilku przypadków". Najpierw wartość, potem switch, a potem gałęzie pattern => result oddzielone przecinkami, z _ jako przypadkiem domyślnym:
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
Jeśli w czasie działania żadna gałąź nie pasuje, rzucany jest SwitchExpressionException, a kompilator ostrzega (CS8509), gdy widzi, że jakieś dane wejściowe nie są obsłużone. Składnię opisuje osobno strona o wyrażeniu switch; reszta tej strony dotyczy wzorców, które możesz umieścić w gałęziach.
Wzorce właściwości (C# 8, rozszerzone w C# 10)
Wzorzec właściwości dopasowuje właściwości obiektu do zagnieżdżonych wzorców, w nawiasach klamrowych. Obiekt musi też być różny od null, więc samo { } oznacza "nie 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
W C# 8 i 9 wymagana jest forma zagnieżdżona: { ShipTo: { Country: "PT" } }. C# 10 pozwala na skrót z kropkami użyty powyżej. Wzorce właściwości działają też z is, dzięki czemu sprawdzenia wielu warunków czyta się jak opis: if (order is { Status: "paid", Items: > 0 }).
Wzorce relacyjne i logiczne (C# 9)
C# 9 dodał porównania (<, <=, >, >=) i łączniki and, or i not, które zamieniają przedziały we wzorce:
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
Odpowiednikiem w C# 7 jest łańcuch if/else if albo switch z klauzulami when (case int s when s >= 90:).
Wzorce krotek i pozycyjne (C# 8)
Przełączanie na kilku wartościach naraz wykonuje się przez switch na krotce:
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
Wzorzec pozycyjny robi to samo dla każdego typu z metodą Deconstruct, w tym dla rekordów: case Point(0, 0): albo p is Rectangle(var w, var h) && w == h.
Wzorce list (C# 11)
Wzorce list dopasowują tablice i listy według ich elementów. .. pasuje do dowolnej liczby elementów (tzw. slice), a każda pozycja elementu może zawierać dowolny inny wzorzec:
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
Która wersja co dodała
| Wzorzec | Przykład | Wersja |
|---|---|---|
| Wzorzec typu ze zmienną | o is Circle c | C# 7.0 |
| Wzorzec stałej | x is null, n is 0 | C# 7.0 |
Wzorzec var | x is var v | C# 7.0 |
Wzorce w etykietach case, strażnicy when | case int n when n < 0: | C# 7.0 |
Wyrażenie switch, odrzucenie _ | x switch { ... } | C# 8 |
| Wzorzec właściwości | { Status: "paid" } | C# 8 |
| Wzorzec krotki | (x, y) switch { (0, 0) => ... } | C# 8 |
| Wzorzec pozycyjny | Point(0, 0) | C# 8 |
| Wzorzec relacyjny | > 100, <= 0 | C# 9 |
| Wzorce logiczne | not null, >= 1 and <= 5, 'a' or 'b' | C# 9 |
| Goły wzorzec typu | case Circle:, Circle => ... | C# 9 |
| Rozszerzony wzorzec właściwości | { ShipTo.City: "Porto" } | C# 10 |
| Wzorzec listy i slice | [first, .., last] | C# 11 |
Typowe błędy
- Kolejność przypadków od ogólnych do szczegółowych.
case Payment p:przedcase CardPayment c:sprawia, że drugi jest nieosiągalny (CS8120). Szczegółowe najpierw. - Oczekiwanie, że wzorzec typu dopasuje null.
null is string sjest fałszywe. Obsłużnullosobnym przypadkiem albo przezis null. - Odczyt zmiennej wzorca tam, gdzie dopasowanie mogło się nie udać. Po
a is int n || ...zmiennannie jest przypisana (CS0165). - Długie switche po typach zamiast metod wirtualnych. Jeśli każda nowa podklasa oznacza edycję tego samego switcha w pięciu miejscach, zachowanie należy do hierarchii klas. Wzorce błyszczą przy danych z zewnątrz (kształty JSON, komunikaty, krotki danych wejściowych) i przy zamkniętych zbiorach typów, które kontrolujesz.
- Zapominanie o przypadku domyślnym w wyrażeniu switch. Niedopasowane dane rzucają wyjątek w czasie działania; czytaj ostrzeżenie CS8509.
Najczęściej zadawane pytania
Czym jest pattern matching w C#?
Dopasowywanie wzorców sprawdza, czy wartość ma określony kształt, i gdy pasuje, może w tym samym kroku wyciągnąć jej części do zmiennych. if (shape is Circle c) sprawdza typ i daje ci otypowane c; order is { Status: "paid", Total: > 100 } sprawdza właściwości. Wzorce pojawiają się w wyrażeniach is, instrukcjach switch i wyrażeniach switch.
Jak używać is ze zmienną w C#?
if (obj is Customer c) { ... } sprawdza, czy obj jest typu Customer (i nie jest null), i przypisuje go do c typu Customer wewnątrz if. Zastępuje to starszą parę var c = obj as Customer; if (c != null). Zmienna jest na pewno przypisana tylko tam, gdzie wiadomo, że test wypadł pozytywnie, więc if (!(obj is Customer c)) return; pozwala używać c po if.
Jaka jest różnica między is null a == null w C#?
x is null zawsze sprawdza samą referencję. x == null wywołuje operator == danego typu, jeśli ten go przeciąża, co zwykle daje ten sam wynik, ale nie jest to gwarantowane. Odwrotny test to x != null, !(x is null) albo x is not null od C# 9.
Jak działa switch z pattern matching i when w C#?
Etykieta case może zawierać wzorzec i strażnika when: case Order o when o.Total > 100:. Przypadek pasuje tylko wtedy, gdy pasuje wzorzec i warunek jest prawdziwy, a przypadki są sprawdzane od góry do dołu, więc bardziej szczegółowe umieszczaj wyżej. Kompilator zgłasza CS8120, gdy przypadek nigdy nie może zostać osiągnięty, bo wcześniejszy już go obejmuje.
Która wersja C# dodała które wzorce?
C# 7.0: wzorce typu, stałej i var w is i case, a także when. C# 8: wyrażenia switch, wzorce właściwości, krotek i pozycyjne. C# 9: wzorce relacyjne (> 5), logiczne (and, or, not) i gołe wzorce typu. C# 10: rozszerzone wzorce właściwości ({ Address.City: "Porto" }). C# 11: wzorce list ([1, .., var last]).