Bezpieczny null-check w C#
Dziś krótki wpis, który być może okaże się dla Ciebie bardzo przydatny. Chodzi o jedną z technik programowania defensywnego, którą bardzo często możesz spotkać w kodzie C# (z uwagi na jego specyfikę), a mianowicie null-check (ogólniej asercja). Spójrz na poniższy kod
class Program { static void Main(string[] args) { Program p = null; if(p == null) { Console.WriteLine("p is null"); } else { Console.WriteLine("p is not null"); } Console.ReadLine(); } }
Wynik działania programu:
Jest to bardzo typowe sprawdzenie czy zmienna typu referencyjnego jest null-em, czy też nie. Wszyscy widzimy to często zaraz po pobraniu danych z DAL (ang. data access layer), pojawia się to także jako „walidacja” danych w kontrolerze ASP.NET itd. Jest to fundament programowania w C#, który każdy doskonale zna i rozumie. Ma on tylko jedną wadę… nie zawsze działa. Zmodyfikujmy nieco powyższy kod:
class Program { static void Main(string[] args) { Program p = null; if(p == null) { Console.WriteLine("p is null"); } else { Console.WriteLine("p is not null"); } Console.ReadLine(); } public static bool operator ==(Program a, Program b) => false; public static bool operator !=(Program a, Program b) => true; }
W programie dodałem przeciążenie dla operatora == oraz != (ten drugi wymusił sam C#, ponieważ operatory porównania muszą być przeciążane parami). Jak widzisz implementacja nakazuje, aby operator == zawsze zwracał wartość false. W związku z powyższym wynik działania programu nie powinien być dla Ciebie zaskoczeniem:
Mimo tego iż zmienna p była null-em, wyrażenie w bloku if (poprawnie) przeprowadziło ewaluację do wartości false, tym samym wypisując zakłamany dla użytkownika komunikat.
W jaki sposób ustrzec się zatem przed ewentualnym przeciążaniem operatora równości? Stosując słowo kluczowe is. Zmodyfikujmy powyższy kod raz jeszcze, wprowadzając odpowiednią korektę:
class Program { static void Main(string[] args) { Program p = null; if(p is null) { Console.WriteLine("p is null"); } else { Console.WriteLine("p is not null"); } Console.ReadLine(); } public static bool operator ==(Program a, Program b) => false; public static bool operator !=(Program a, Program b) => true; }
Tym razem wynik (mimo przeciążenia operatorów) jest już taki, jakiego się spodziewaliśmy:
Ten ficzer jest stosunkowo nowy w języku, ponieważ został dodany w wersji 7.0 przy okazji pattern matching-u. Jest to tzw. constant pattern. Mam jednak wrażenie, że mimo wszystko wiele osób najzwyczajniej w świecie go przegapiła, a szkoda bo może nie raz uratować nam skórę… i uchronić przed długimi sesjami debugowania.