Operator is w C# przydaje się wtedy, gdy chcesz bezpiecznie sprawdzić typ, porównać wartość z wzorcem albo zawęzić gałąź kodu bez ryzykownego rzutowania. Dla mnie to jedno z najpraktyczniejszych narzędzi w pracy z obiektami, nullable i pattern matchingiem, bo pozwala pisać warunki, które są jednocześnie czytelne i odporne na błędy. W tym artykule rozkładam go na czynniki pierwsze: od podstaw, przez przykłady, po różnice względem as, == i klasycznego castu.
Najkrócej o operatorze is w C#
-
issprawdza, czy wartość pasuje do typu albo wzorca, a wynikiem jest zawszebool. - Najczęściej używa się go do bezpiecznego zawężania typu w
ifiswitch. -
To nie jest operator porównania wartości - dla tekstów użyj
==alboEquals. -
isdobrze współgra z pattern matchingiem, w tym znull, właściwościami i warunkami relacyjnymi. - Nie uwzględnia konwersji zdefiniowanych przez użytkownika, więc nie zastępuje rzutowania.
Czym jest operator is i do czego naprawdę służy
Najprościej mówiąc, is odpowiada na pytanie: „czy ta wartość pasuje do tego typu albo wzorca?”. Jeśli warunek przejdzie, mogę od razu pracować na bezpiecznie zawężonych danych, zamiast zgadywać i liczyć na szczęście. To szczególnie ważne w kodzie, w którym obiekt może pochodzić z różnych źródeł, być opakowany jako object albo reprezentować jedną z kilku klas pochodnych.
W praktyce używam go w trzech sytuacjach: gdy sprawdzam typ w hierarchii klas, gdy chcę odsiać null i gdy dopasowuję strukturę danych. W nowoczesnym C# ten operator jest częścią pattern matchingu, więc nie kończy się na samym „czy to jest ten typ” - potrafi też sprawdzać właściwości, zakresy i kombinacje warunków.
Jeśli w danym miejscu kodu potrafisz nazwać oczekiwanie jednym zdaniem, zwykle da się to wyrazić właśnie przez is. Gdy oczekiwanie zaczyna brzmieć jak kilka zdań naraz, lepiej przygotować się na przykład praktyczny.
Jak działa na typach, nullu i wzorcach
Tu zaczyna się najciekawsza część. is nie porównuje „czy obiekty są identyczne”, tylko sprawdza zgodność z typem lub wzorcem. Dzięki temu możesz napisać warunek, który jednocześnie filtruje dane i od razu wyciąga je do lokalnej zmiennej.
object value = "Ala";
if (value is string text)
{
Console.WriteLine(text.ToUpper());
}
int? maybeNumber = 7;
if (maybeNumber is int number)
{
Console.WriteLine(number + 1);
}
if (value is not null)
{
Console.WriteLine("Wartość istnieje");
}W pierwszym przykładzie obiekt typu object zostaje sprawdzony pod kątem string, a przy okazji trafia do zmiennej text. To wygodniejsze niż osobny test i osobny cast. W drugim przykładzie widać ważną rzecz: int? może zostać dopasowany do int, jeśli faktycznie zawiera wartość. W trzecim przypadku is not null daje czytelny test nullowy, który dobrze współpracuje z analizą null-state w kompilatorze.
Operator uwzględnia też boxing i unboxing, ale nie traktuje cudów z konwersją numeryczną jak magicznej drogi na skróty. Jeśli zboxedujesz int, to boxed is int zwróci true, ale boxed is long już nie, mimo że oba typy są liczbami całkowitymi.
int i = 27;
object boxed = i;
Console.WriteLine(boxed is int); // True
Console.WriteLine(boxed is long); // FalseWzorce nie kończą się na typach. Możesz sprawdzać kształt danych, na przykład właściwości obiektu albo zakresy wartości. To jest właśnie ten moment, w którym is przestaje być prostym testem, a staje się bardzo zwięzłym narzędziem do opisywania warunków biznesowych.
DateTime date = default;
bool matchesPattern =
date is { Month: 10, Day: <= 7, DayOfWeek: DayOfWeek.Friday };W takim zapisie warunek jest czytelniejszy niż kilka zagnieżdżonych ifów. A skoro widać już, co is potrafi, naturalnie warto porównać go z narzędziami, które ludzie mylą z nim najczęściej.
Czym różni się od as, == i rzutowania
To jedna z tych różnic, które oszczędzają sporo błędów w kodzie. is sprawdza zgodność, as próbuje bezpiecznie przekonwertować, == porównuje wartości albo referencje zależnie od typu, a rzutowanie wymusza konkretny typ i może skończyć się wyjątkiem. Jeśli pomylisz te narzędzia, kod zwykle działa tylko do pierwszego mniej oczywistego przypadku.
| Narzędzie | Co robi | Co zwraca | Ryzyko | Kiedy używać |
|---|---|---|---|---|
is |
Sprawdza typ albo wzorzec | bool |
Niskie, bo niczego nie wymusza | Gdy chcesz warunkowo przejść dalej |
as |
Próbuje przekonwertować do typu referencyjnego lub nullable | Obiekt lub null
|
Łatwo zapomnieć o sprawdzeniu wyniku | Gdy potrzebujesz referencji do dalszej pracy |
== |
Porównuje wartości lub referencje zależnie od typu | bool |
Łatwo pomylić z testem typu | Gdy interesuje cię równość, nie typ |
| Rzutowanie | Wymusza konkretny typ | Wartość danego typu | Może rzucić InvalidCastException
|
Gdy masz pewność co do typu danych |
Ani is, ani as nie uwzględniają konwersji zdefiniowanych przez użytkownika. Jeśli zależy ci na takim przejściu, użyj jawnego castu. Warto też pamiętać, że dla napisów zwykle używam == albo Equals, a nie is. Ten operator nie służy do sprawdzania, czy dwa stringi mają tę samą treść.
Ta różnica bywa banalna na prostych przykładach, ale w większych systemach decyduje o tym, czy kod jest przewidywalny. I właśnie tu pojawiają się pułapki, które widać dopiero po kilku tygodniach pracy.
Najczęstsze błędy, przez które is zaskakuje
Najczęstszy błąd jest prosty: ktoś traktuje is jak operator równości. To prowadzi do złych decyzji zwłaszcza przy stringach, liczbach i obiektach z własnymi regułami porównywania. Jeśli w grę wchodzi wartość, a nie typ, od razu zatrzymuję się i pytam, czy na pewno chodzi o pattern matching.
-
Mylenie typu z wartością -
isnie odpowie, czy dwie zmienne mają tę samą treść. - Oczekiwanie konwersji niestandardowych - operator nie korzysta z user-defined conversions.
-
Zakładanie, że wzorzec typu dopasuje
null- deklaracyjny pattern nie przejdzie dlanull. -
Brak nawiasów przy złożonych wzorcach - przy
not,andiorłatwo o inny wynik niż zamierzony. -
Zbyt długie warunki - kiedy logika rośnie, lepiej przejść na osobną metodę albo
switch.
// Błędna intencja: to nie jest test "czy litera nie jest mała"
static bool IsNotLowerCaseLetter(char c) => c is not >= 'a' and <= 'z';
// Czytelna wersja
static bool IsNotLowerCaseLetterFixed(char c) => c is not (>= 'a' and <= 'z');Ten przykład dobrze pokazuje, że w pattern matchingu nawiasy nie są ozdobą, tylko narzędziem precyzji. Im bardziej złożony warunek, tym większa szansa, że bez nawiasów kod przestanie znaczyć to, co miał znaczyć. A skoro da się już wskazać błędy, czas przejść do praktyki: jak pisać z is kod, który naprawdę pomaga, zamiast tylko „sprytnie wyglądać”.
Jak pisać czytelny kod z is w nowoczesnym C#
W dobrym kodzie is nie jest sztuczką, tylko skrótem myślowym. Ja zwykle stosuję trzy proste zasady: sprawdzam tylko to, co jest mi naprawdę potrzebne, nie rozciągam jednego warunku na pół ekranu i od razu korzystam z nowej zmiennej, jeśli pattern ją wyłuskuje.
- Używaj deklaracji typu, gdy po teście od razu pracujesz na konkretnym obiekcie.
- Sięgaj po
is not null, gdy chcesz czytelnego, jednoznacznego testu nullowego. - Jeśli warunek ma kilka gałęzi, rozważ
switchzamiast rozbudowanegoif. - Dodawaj nawiasy, gdy łączysz
not,andior. - Nie używaj
isdo porównywania napisów, liczb ani innych wartości, jeśli naprawdę interesuje cię ich równość.
string Handle(object input)
{
if (input is not string text)
return "Nie jest tekstem";
if (text.Length == 0)
return "Pusty tekst";
return text.Trim();
}Taki zapis jest lepszy niż zagnieżdżanie kilku rzutowań albo sprawdzanie typu i wracanie do niego po chwili. W praktyce warunek mówi wprost, co akceptuję, a co odrzucam, dzięki czemu kod mniej zaskakuje podczas przeglądu i późniejszej rozbudowy. Jeśli trzymasz się tych zasad, decyzja, czy użyć is, zwykle staje się oczywista jeszcze przed napisaniem kodu.
Co naprawdę warto zapamiętać z operatora is
Najważniejsza rzecz jest prosta: is sprawdza zgodność z typem albo wzorcem, a nie „czy coś jest równe czemuś innemu”. Jeśli zapamiętasz tylko to rozróżnienie, unikniesz większości typowych pomyłek. Reszta to już kwestia dopasowania narzędzia do sytuacji.
W codziennej pracy traktuję ten operator jak bezpieczny filtr. Najpierw pozwala mi odsiać niepasujące dane, potem - jeśli trzeba - od razu pracować na zawężonym typie bez dodatkowych castów i bez ryzyka przypadkowego wyjątku. To właśnie dlatego dobrze napisany warunek z is często wygląda krócej, a jednocześnie mówi więcej niż starsze, bardziej rozwlekłe konstrukcje.
Jeżeli budujesz kod w C# dla ludzi, którzy będą go utrzymywać po tobie, dbaj o prostotę wzorców i jasne nazewnictwo zmiennych wyciąganych z is. Wtedy ten operator robi dokładnie to, do czego został stworzony: upraszcza decyzję i zmniejsza liczbę miejsc, w których można popełnić błąd.