Sprawdzenia parametrów null w C# 10 i .NET 6 w niecałe 10 minut
Podczas pracy w języku C# programiści często spotykają się z sytuacją, w której do metod przekazywane są argumenty o wartości null. Może to prowadzić do nieoczekiwanych zachowań, błędów wykonania, a nawet nieobsługiwanych wyjątków, jeśli nie zostanie odpowiednio obsłużone. W swoim filmie "Null Parameter Checks in C# 10 and .NET 6 in 10 Minutes or Less" Tim Corey wyjaśnia, w jaki sposób C# 10 uprościł obsługę wartości null za pomocą klasy ArgumentNullException.
W tym artykułe przejdziemy przez film dokładnie tak, jak wyjaśnia to Tim, aby zrozumieć, jak działają sprawdzania wartości null i jak zostały one ulepszone w nowoczesnym C#.
Przygotowanie – sprawdzanie parametrów null
Tim zaczyna od stworzenia prostej aplikacji konsolowej .NET 6, aby pokazać, jak w języku C# obsługuje się sprawdzanie parametrów null. Usuwa on kod szablonowy i deklaruje zmienną typu string z możliwością wartości null:
string? info = null;
string? info = null;
Ten obiekt null reprezentuje scenariusz, w którym wywołanie metody może otrzymać argument, który nie został poprawnie zainicjowany. Następnie Tim definiuje prostą metodę:
void SayHi(string message)
{
Console.WriteLine($"Witam {message}");
}
void SayHi(string message)
{
Console.WriteLine($"Witam {message}");
}
Przekazuje zmienną info do metody:
SayHi(info);
SayHi(info);
Po uruchomieniu programu wyświetla się po prostu:
Witam
Nie występuje żaden wyjątek, ponieważ operacja łączenia ciągów znaków akceptuje wartość null i traktuje ją jak pusty ciąg znaków. Jednak, jak zauważa Tim, w większości rzeczywistych metod może to powodować sytuacje podatne na błędy, w których odwołanie do wartości null prowadzi do problemów w dalszej części procesu lub do wyjątku NullReferenceException w dalszej części stosu wywołań.
Aby stworzyć solidny kod, programiści powinni sprawdzać poprawność argumentów i upewniać się, że każda metoda oczekuje prawidłowych danych wejściowych, które nie są null.
Tradycyjna kontrola wartości null przed wersją C# 10
Tim wyjaśnia, że przed wprowadzeniem C# 10 dobrą praktyką było ręczne sprawdzanie parametrów i zgłaszanie wyjątku ArgumentNullException w razie potrzeby. Wewnątrz metody SayHi dodaje:
if (message is null)
throw new ArgumentNullException(nameof(message));
if (message is null)
throw new ArgumentNullException(nameof(message));
Ta kontrola gwarantuje, że w przypadku przekazania argumentu o wartości null program natychmiast zgłasza wyjątek klasy ArgumentNullException.
Kiedy Tim uruchamia kod, komunikat o błędzie wyraźnie pokazuje:
System.ArgumentNullException: Value cannot be null. (Parameter 'message')
System.ArgumentNullException: Value cannot be null. (Parameter 'message')
Ten nieobsługiwany wyjątek wskazuje, że metoda otrzymała nieprawidłowy argument. Nazwa parametru — w tym przypadku "message" — jest automatycznie wyświetlana w wynikach, co pomaga programiście dokładnie zidentyfikować, który argument spowodował problem.
Tim zauważa, że ta ręczna kontrola działa doskonale w środowisku .NET Framework oraz we wcześniejszych wersjach .NET Core. Jednak w przypadku wielu parametrów tekst jest zbyt rozbudowany i powtarzalny. Każdy dodatkowy parametr wymagający walidacji powoduje dodanie trzech lub czterech wierszy kodu, co powoduje zagracenie treści metody.
Uproszczone podejście w C# 10 – ArgumentNullException.ThrowIfNull()
W tym momencie Tim przedstawia nowoczesną składnię języka C# 10. Zamiast pisać wiele wierszy dla każdego parametru, programiści mogą teraz napisać jeden wiersz sprawdzający wartość null:
ArgumentNullException.ThrowIfNull(message);
ArgumentNullException.ThrowIfNull(message);
Tim wyjaśnia, że ten jednowierszowy kod wewnętrznie wykonuje tę samą logikę walidacji. Jeśli przekazany argument ma wartość null, metoda automatycznie zgłasza wyjątek — tak jak poprzednio — ale z dużo bardziej przejrzystą składnią.
Po ponownym uruchomieniu programu pojawia się następujący wynik:
System.ArgumentNullException: Value cannot be null. (Parameter 'message')
System.ArgumentNullException: Value cannot be null. (Parameter 'message')
Zachowanie pozostaje identyczne, ale teraz składnia została uproszczona. Nie ma potrzeby ręcznego tworzenia instancji nowego obiektu ArgumentNullException za pomocą konstruktora ani ręcznego określania parametru paramName. Nazwa parametru wywołującego jest automatycznie wnioskowana przez kompilator.
Tim podkreśla, że to ulepszenie pozwala zaoszczędzić czas i zmniejsza ryzyko błędu ludzkiego wynikającego z odwołania się do niewłaściwej nazwy parametru.
Przykład: Wiele parametrów
Następnie Tim porównuje tradycyjną metodę z nową w kontekście obsługi wielu parametrów.
Przed wersją C# 10 programista mógłby napisać:
if (name is null)
throw new ArgumentNullException(nameof(name));
if (email is null)
throw new ArgumentNullException(nameof(email));
if (password is null)
throw new ArgumentNullException(nameof(password));
if (name is null)
throw new ArgumentNullException(nameof(name));
if (email is null)
throw new ArgumentNullException(nameof(email));
if (password is null)
throw new ArgumentNullException(nameof(password));
Dzięki ulepszeniom w C# 10 te same sprawdzania można teraz zapisać w bardziej zwięzły sposób:
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(email);
ArgumentNullException.ThrowIfNull(password);
ArgumentNullException.ThrowIfNull(name);
ArgumentNullException.ThrowIfNull(email);
ArgumentNullException.ThrowIfNull(password);
Ta zmiana to nie tylko mniej linii kodu — sprawia, że metody są bardziej przejrzyste, czytelne i łatwiejsze w utrzymaniu. Tim zauważa, że jeśli metoda oczekuje trzech parametrów, teraz potrzebujesz tylko trzech sprawdzeń wartości null, zamiast dwunastu linii kodu.
Dłączego to ma znaczenie – bezpieczniejszy i bardziej niezawodny kod
Tim zwraca uwagę, że chociaż ręczne przekazywanie wartości null do ciągu znaków w demonstracji może nie być realistyczne, odzwierciedla to rzeczywiste problemy. Interfejsy API, dane wprowadzane przez użytkownika, procesy deseryalizacji lub operacje zwracające obiekty mogą nieoczekiwanie generować obiekty null.
Dodając sprawdzanie wartości null na początku metody, zapobiegasz nieoczekiwanym zachowaniom i zapewniasz szybką detekcję błędów — co oznacza, że program zgłasza wyjątek na wczesnym etapie stosu wywołań, zanim problem się rozprzestrzeni.
Wspomina również, że ta nowa składnia pomaga zachować spójne wzorce obsługi błędów we wszystkich metodach. Zachęca do pisania solidnego kodu, który jest odporny na nieprawidłowe dane wejściowe i nieinstancjonowane obiekty.
Szczegóły dotyczące ArgumentNullException
Tim podkreśla, że ArgumentNullException jest częścią przestrzeni nazw System i dziedziczy po klasie ArgumentException. Jest on generowany, gdy do metody, która nie akceptuje odwołania null jako prawidłowego argumentu, przekazywane jest odwołanie null.
Zazwyczaj obejmuje:
-
Nazwa parametru, który spowodował problem.
-
Jasny komunikat o błędzie: "Wartość nie może być null".
- Stos wywołań, pokazujący miejsce wystąpienia wyjątku.
Przykład Tima pokazuje, w jaki sposób .NET 6 nadal zachowuje tę logikę, jednocześnie ulepszając składnię.
Autor krótko zauważa również, że programiści mogą łączyć te sprawdzania z innymi funkcjami, takimi jak operator scałania wartości null (??) lub wartości domyślne, aby zapewnić logikę awaryjną — na przykład:
message ??= "Default message";
message ??= "Default message";
Pozwala to metodzie przypisać wartość domyślną zamiast generować wyjątek, jeśli jest to pożądane.
Znaczenie sprawdzania wartości null w obsłudze błędów
Tim przypomina programistom, że mimo iż C# obsługuje obecnie typy referencyjne z możliwością wartości null, sprawdzanie wartości null w czasie wykonywania nadal jest niezbędne. Kompilator nie jest w stanie wykryć wszystkich problemów związanych z odwołaniami do wartości null — zwłaszcza gdy dane pochodzą ze źródeł zewnętrznych lub metod pozostających poza kontrolą użytkownika.
Podkreśla, że dobrą praktyką jest sprawdzanie argumentów i zgłaszanie wyjątku ArgumentNullException w odpowiednich przypadkach, aby zapewnić, że metoda zostanie wykonana tylko wtedy, gdy dane wejściowe są prawidłowe. Pozwala to zminimalizować liczbę błędów wykonawczych, upraszcza obsługę błędów i zapobiega cichym awariom.
Podsumowanie i perspektywy na przyszłość
Podsumowując, Tim zauważa, że C# 11 wprowadzi jeszcze więcej ulepszeń, ale na razie ArgumentNullException.ThrowIfNull() jest jednym z najlepszych drobnych ulepszeń w zakresie obsługi błędów i bezpieczeństwa kodu w .NET 6.
Zachęca programistów do wypróbowania tego podejścia w swoich własnych projektach i sprawdzenia, o ile bardziej przejrzyste stają się ich metody. Na koniec zadaje pytanie: "Co sądzisz o tej zmianie w C# 10?"
Podsumowanie
Film Tima Coreya jasno pokazuje, że metoda ArgumentNullException.ThrowIfNull() stanowi proste, ale potężne ulepszenie w C# 10. Ogranicza ona podatny na błędy kod ręczny, zapewnia poprawność argumentów i sprawia, że programy szybko zgłaszają błąd w przypadku przekazania argumentu null.
Stosując tę metodę konsekwentnie, programiści mogą tworzyć solidny, czytelny i łatwy w utrzymaniu kod, który sprawnie obsługuje wyjątki i pozwala uniknąć subtelnych błędów związanych z odwołaniami do wartości null.
Krótko mówiąc, ilekroć metoda oczekuje prawidłowego parametru i chcesz zapobiec przekazywaniu argumentów null, postępuj zgodnie z przykładem Tima Coreya i użyj:
ArgumentNullException.ThrowIfNull(parameterName);
ArgumentNullException.ThrowIfNull(parameterName);
To czystszy, bezpieczniejszy i nowocześniejszy sposób ochrony kodu przed wartościami null i błędami wykonania w .NET 6 i nowszych wersjach.
