Nullable Reference Types w C# — jak wyłapać NullReferenceException przed uruchomieniem
NullReferenceException to najczęściej spotykany wyjątek w kodzie .NET — i przez dekady C# nie dawał kompilatorowi żadnego sposobu, żeby ostrzec cię o nim wcześniej niż w runtime. Nullable Reference Types (C# 8+) to zmieniają: kompilator analizuje statycznie, gdzie null może się pojawić, i podkreśla to na żółto w edytorze, zanim jeszcze uruchomisz program.
Problem: typy referencyjne zawsze mogły być null
public class Uzytkownik
{
public string Imie { get; set; }
}
Uzytkownik? uzytkownik = ZnajdzUzytkownika(id);
Console.WriteLine(uzytkownik.Imie.ToUpper()); // kompiluje się, wybucha w runtime jeśli null
Przed C# 8 kompilator nie widział różnicy między string, który zawsze ma wartość, a string, który czasem jest null. Każdy typ referencyjny (class, string, interfejsy) mógł być null, a jedynym sposobem, żeby się o tym dowiedzieć, było uruchomienie kodu i czekanie na wyjątek.
Włączenie Nullable Reference Types
W pliku .csproj:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
Albo per plik, na górze pliku .cs:
#nullable enable
Nowe projekty w .NET 6+ mają to włączone domyślnie. Po włączeniu, string i string? przestają znaczyć to samo dla kompilatora.
Dwa typy: nullable i non-nullable
string imie = "Anna"; // kompilator: ta zmienna NIGDY nie powinna być null
string? pseudonim = null; // kompilator: ta zmienna MOŻE być null — sprawdzaj przed użyciem
imie = null; // ⚠️ warning: przypisanie null do non-nullable
Console.WriteLine(pseudonim.Length); // ⚠️ warning: możliwe wyłuskanie null
To wciąż tylko ostrzeżenia (warnings), nie błędy kompilacji — kod się skompiluje i uruchomi. Ale IDE (Visual Studio, Rider, VS Code) podświetli te miejsca na żółto, więc widzisz potencjalny NullReferenceException zanim klikniesz “Uruchom”, nie po zgłoszeniu buga przez użytkownika.
Jak kompilator śledzi przepływ null
Kompilator analizuje przepływ sterowania — jeśli sprawdzisz null wcześniej, ostrzeżenie znika:
string? pseudonim = PobierzPseudonim();
if (pseudonim is not null)
{
Console.WriteLine(pseudonim.Length); // ✅ brak ostrzeżenia — kompilator wie, że tu nie jest null
}
Console.WriteLine(pseudonim.Length); // ⚠️ warning — poza blokiem if, znowu może być null
To samo działa z ??, wzorcami dopasowania i wczesnym return:
string? pseudonim = PobierzPseudonim();
if (pseudonim is null) return;
Console.WriteLine(pseudonim.Length); // ✅ kompilator śledzi, że po tym return nie jest null
Operator wybaczający null (!) — używaj oszczędnie
string? konfiguracja = Environment.GetEnvironmentVariable("APP_CONFIG");
string sciezka = konfiguracja!; // "wiem lepiej niż kompilator, to nie jest null"
Wykrzyknik (null-forgiving operator) wyłącza ostrzeżenie, obiecując kompilatorowi, że wartość na pewno nie będzie null w tym miejscu. To ucieczka awaryjna, nie rozwiązanie — jeśli się mylisz, dostajesz dokładnie ten sam NullReferenceException, przed którym cała ta funkcja miała cię chronić. Używaj tylko wtedy, gdy naprawdę masz pewność wynikającą z logiki, której kompilator nie widzi (np. tuż po sprawdzeniu w innej metodzie).
Konstruktory i wymagane właściwości
Częsta pułapka na start:
public class Zamowienie
{
public string NumerZamowienia { get; set; } // ⚠️ warning: niezainicjalizowana non-nullable właściwość
}
Kompilator ostrzega, bo NumerZamowienia deklarujesz jako “zawsze ma wartość”, ale konstruktor domyślny niczego nie ustawia. Trzy poprawne rozwiązania:
// 1) required (C# 11+) — wymusza ustawienie przy tworzeniu obiektu
public class Zamowienie
{
public required string NumerZamowienia { get; set; }
}
// 2) Konstruktor ustawiający wartość
public class Zamowienie
{
public string NumerZamowienia { get; }
public Zamowienie(string numerZamowienia) => NumerZamowienia = numerZamowienia;
}
// 3) Świadomie nullable, jeśli wartość naprawdę może nie istnieć
public class Zamowienie
{
public string? NumerZamowienia { get; set; }
}
Podsumowanie
Nullable Reference Types nie eliminują NullReferenceException — to wciąż ostrzeżenia, nie twarde gwarancje, i operator ! pozwala je obejść. Ale przesuwają moment wykrycia problemu z “użytkownik zgłasza crasha na produkcji” na “żółte podkreślenie w edytorze, zanim jeszcze uruchomisz kod”. W praktyce: włącz <Nullable>enable</Nullable> w każdym nowym projekcie, traktuj ostrzeżenia poważnie zamiast je ignorować, i sięgaj po ! tylko wtedy, gdy naprawdę wiesz więcej niż kompilator.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →