C# i NULL NullReferenceException. Ile razy w swojej karierze widziałeś ten błąd. Wielu programistów i geniuszy architektury programowania od dawna się zastanawia, że może wartość NULL w typach referencyjnych to duży problem.

W C# nie ma wielokrotnego dziedziczenia. Null jest tym wyjątkiem, bo technicznie dziedziczny on po wszystkich wartościach referencyjnych. Brzmi to, jak łamanie jakieś zasady projektowej

Niektórzy mają śmiałość mówić, że jest to problem wart miliardy dolarów

Wyjaśnijmy ich punkt widzenia

Gdzie informacja, jakie wartości mogą wystąpić przy zwrocie metody

Załóżmy, że metoda aplikacji, do której nie masz dostępu, zwraca typ referencyjny np. string.

Teraz pytanie, czy bez testowania, czytania dokumentacji kodu źródłowego masz wiedze czy wartość NULL z tej metody jest wartością legalną.

Gdybyś mógł oznaczyć string w taki sposób string?. To byś miał świadomość, kiedy NULL wystąpi, a kiedy nie ma racji bytu

Teraz gdy nie masz pewność co otrzymasz

To sprowadza się do kolejnego problemu. Sprawdzania dla każdego typu referencyjnego czy nie ma on przypisanej wartości NULL

var book = this._bookStoreRepository.Get(1234);
if (book != null)
{
    // rest of the logic here.
}
else
{
    // logic for the null case here.
}

Ta praktyka nazywa się programowanie defensywny ("defensive programming"). Nie ufasz niczemu i by mieć pewność, że przechwycisz każdą możliwość wartości NULL to wstawiasz takie IF-y wszędzie

Wydaje się to głupie

Jednakże w swoje karierze programisty i być może Twojej pamiętasz, jaka to fajna jest zabawa, gdy na produkcji wyskakuje wyjątek NullReferenceException, a ty nie wiesz gdzie

Nagle taki IF by się przydał, bo łatwiej byś znalazł później ten problem w logach

Null kusi

Jako programista Null także Ciebie kusi, by go zwrócić, gdy nie masz pewność co dokładnie, powinna zwrócić Twoja metoda w jakimś dziwnym przypadku. 

Zawsze możesz zwrócić NULL i przesłać problem wyżej.

Pytanie tylko, czy ten NULL mówi co się stało innemu programiście

Rozwiązanie C# 6.0

W C# 6.0 pojawił się Elvis operator ? nazywany także Null-conditional 

int? length = people?.Length; // null if people is null
Person first = people?[0];    // null if people is null

if (myDelegate?.Invoke(args) ?? false) { … } 

Oczywiście to nie rozwiązuje problemu. Ten wynalazek tylko sprawia, że kod jest bardziej czytelny

Twórcy C# stwierdzi, że pora na prawdziwe rozwiązanie, czyli na pozbycie się wszystkich domyślnych NULL-i w typach referencyjnych. Jednak by nie wywalać kodu, który już działa to stworzyli mechanizm znaczników

Rozwiązanie C# 8.0

C# 8 obecnie działa domyślnie dla .NET Standard 2.1 i .NET Core 3.x

W swoim projekcie jednak muszę powiedzieć, że chce skorzystać z opcji ustalania, że każdy typ referencyjny domyślnie nie powinien przechowywać wartości NULL

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <Nullable>Enable</Nullable>
  </PropertyGroup>
</Project>

W praktyce działa to tak

String jest typem referencyjnym, ale wiem, że ten string "Non_nullablereference" zawsze będzie miał jakiś tekst, więc tylko go deklaruje.

Natomiast dla stringu który będzie miał wartości NULL, dodaje do jego typu znak zapytania.

class Program
{
    private static string Non_nullablereferencetype = "Cezary";
    private static string? nullablereferencetype = null;

    static void Main(string[] args)
    {
#pragma warning disable CS8625
        // Cannot convert null literal to non-nullable reference type.
        Non_nullablereferencetype = null;
#pragma warning restore CS8625
        // Cannot convert null literal to non-nullable reference type.

        nullablereferencetype = "Adah";
    }

}

Gdy próbuje przypisać wartość null dla typu, który go ma nie mieć to, dostaje ostrzeżenie od kompilatora.

Prawdopodobnie, gdybym zmienił coś w ustawieniach kompilatora, to te ostrzeżenie zamieniłoby się na błąd uniemożliwiający mi kompilacje aplikacji.

Warning when string can be NULL

Dodatkowo istnieje jeszcze operator wybaczający NULL. Jest nim wykrzyknik 

null-forgiving operator

Alternatywy

Można skorzystać ze wzorca projektowego Option<T>, by mieć spokój z tymi Nullami

https://github.com/louthy/language-ext

Dodatkowo jest projekt na GitHubie który ma metody i klasy generyczne na ten problem

Zamiast wyjątków i null może chcesz zwracać string z błędem, gdy coś pójdzie nie tak z typem "Either<int,string>"