Czy w języku C # są jakieś dobre powody (inne niż lepszy komunikat o błędzie) do dodawania sprawdzeń wartości null parametrów do każdej funkcji, w której null nie jest prawidłową wartością? Oczywiście kod, który używa s, i tak zgłosi wyjątek. Takie kontrole powodują, że kod jest wolniejszy i trudniejszy w utrzymaniu.
void f(SomeType s)
{
if (s == null)
{
throw new ArgumentNullException("s cannot be null.");
}
// Use s
}
Odpowiedzi:
Tak, istnieją dobre powody:
NullReferenceException
A teraz co do twoich zastrzeżeń:
I dla twojego zapewnienia:
Naprawdę? Rozważać:
void f(SomeType s) { // Use s Console.WriteLine("I've got a message of {0}", s); }
To używa
s
, ale nie zgłasza wyjątku. Jeśli nieważne jest,s
aby mieć wartość null, a to wskazuje, że coś jest nie tak, wyjątek jest tutaj najbardziej odpowiednim zachowaniem.Teraz gdzie można umieścić te weryfikację argument jest zupełnie inna sprawa. Możesz zdecydować się zaufać całemu kodowi z własnej klasy, więc nie przejmuj się prywatnymi metodami. Możesz zdecydować się zaufać reszcie zespołu, więc nie przejmuj się metodami wewnętrznymi. Prawie na pewno powinieneś zweryfikować argumenty dla metod publicznych.
Na marginesie: przeciążenie konstruktora jednoparametrowego
ArgumentNullException
powinno być tylko nazwą parametru, więc test powinien wyglądać następująco:if (s == null) { throw new ArgumentNullException("s"); }
Alternatywnie możesz utworzyć metodę rozszerzającą, pozwalającą na nieco bardziej zwięzłe:
s.ThrowIfNull("s");
W mojej wersji (generycznej) metody rozszerzenia sprawiam, że zwraca oryginalną wartość, jeśli nie jest to null, pozwalając ci pisać takie rzeczy, jak:
this.name = name.ThrowIfNull("name");
Możesz także mieć przeciążenie, które nie przyjmuje nazwy parametru, jeśli nie przejmujesz się tym zbytnio.
źródło
ThrowIfEmpty
naICollection
Debug.Assert
. Jeszcze ważniejsze jest wyłapywanie błędów w produkcji (zanim zepsują one rzeczywiste dane) niż w rozwoju.throw new ArgumentNullException(nameof(s))
Zgadzam się z Jonem, ale dodałbym do tego jedną rzecz.
Moje podejście do tego, kiedy należy dodać jawne kontrole null, opiera się na następujących przesłankach:
throw
oświadczenia są oświadczeniami .if
jest stwierdzenie .throw
inif (x == null) throw whatever;
Jeśli nie ma możliwości wykonania tej instrukcji, nie można jej przetestować i należy ją zastąpić
Debug.Assert(x != null);
.Jeśli istnieje możliwy sposób wykonania tej instrukcji, napisz ją, a następnie napisz test jednostkowy, który ją sprawdza.
Jest to szczególnie ważne, że metody publiczne typach publicznych sprawdzić swoje argumenty w ten sposób; nie masz pojęcia, jakie szalone rzeczy będą robić Twoi użytkownicy. Daj im "hej ty głupcze, robisz to źle!" wyjątek tak szybko, jak to możliwe.
Natomiast metody prywatne typów prywatnych są dużo bardziej prawdopodobne w sytuacji, gdy kontrolujesz argumenty i mogą mieć silną gwarancję, że argument nigdy nie będzie zerowy; użyj potwierdzenia, aby udokumentować tę niezmienność.
źródło
Używam tego od roku:
_ = s ?? throw new ArgumentNullException(nameof(s));
To oneliner, a discard (
_
) oznacza, że nie ma niepotrzebnego przydziału.źródło
Bez wyraźnego
if
czeku, to może być bardzo trudne, aby dowiedzieć się, co byłonull
, jeśli nie jesteś właścicielem kodu.Jeśli otrzymasz plik
NullReferenceException
z wnętrza biblioteki bez kodu źródłowego, prawdopodobnie będziesz mieć wiele problemów ze zrozumieniem, co zrobiłeś źle.Te
if
sprawdzenia nie spowalniają znacząco kodu.Zauważ, że parametr dla
ArgumentNullException
konstruktora to nazwa parametru, a nie komunikat.Twój kod powinien być
if (s == null) throw new ArgumentNullException("s");
Napisałem fragment kodu, aby to ułatwić:
<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>Check for null arguments</Title> <Shortcut>tna</Shortcut> <Description>Code snippet for throw new ArgumentNullException</Description> <Author>SLaks</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> <SnippetType>SurroundsWith</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>Parameter</ID> <ToolTip>Paremeter to check for null</ToolTip> <Default>value</Default> </Literal> </Declarations> <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$"); $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>
źródło
Możesz rzucić okiem na Kontrakty kodu, jeśli potrzebujesz ładniejszego sposobu, aby upewnić się, że nie otrzymujesz żadnych obiektów zerowych jako parametru.
źródło
Główną korzyścią jest to, że od samego początku jasno określasz wymagania swojej metody. To wyjaśnia innym programistom pracującym nad kodem, że wysyłanie wartości null do metody wywołującej jest naprawdę błędem.
Sprawdzenie zatrzyma również wykonanie metody przed wykonaniem jakiegokolwiek innego kodu. Oznacza to, że nie będziesz musiał martwić się o modyfikacje dokonane metodą, która nie została ukończona.
źródło
Oszczędza to trochę debugowania, kiedy trafisz na ten wyjątek.
ArgumentNullException wyraźnie stwierdza, że to „s” ma wartość null.
Jeśli nie masz tego sprawdzania i nie pozwolisz, aby kod działał, otrzymasz NullReferenceException z jakiegoś niezidentyfikowanego wiersza w tej metodzie. W kompilacji wydania nie otrzymujesz numerów linii!
źródło
Oryginalny kod:
void f(SomeType s) { if (s == null) { throw new ArgumentNullException("s cannot be null."); } // Use s }
Przepisz to jako:
void f(SomeType s) { if (s == null) throw new ArgumentNullException(nameof(s)); }
[Edytuj] Powodem przepisywania za pomocą
nameof
jest to, że pozwala na łatwiejszą refaktoryzację. Jeśli nazwa zmiennejs
kiedykolwiek się zmieni, komunikaty debugowania również zostaną zaktualizowane, podczas gdy jeśli po prostu zakodujesz na stałe nazwę zmiennej, to ostatecznie stanie się nieaktualna, gdy aktualizacje będą dokonywane w czasie. To dobra praktyka stosowana w przemyśle.źródło
int i = Age ?? 0;
Na przykład:
if (age == null || age == 0)
Lub:
if (age.GetValueOrDefault(0) == 0)
Lub:
if ((age ?? 0) == 0)
Lub trójskładnikowy:
int i = age.HasValue ? age.Value : 0;
źródło