Opis możliwości dopuszczania wartości null

Ukończone

Jeśli jesteś deweloperem platformy .NET, prawdopodobnie napotkasz element System.NullReferenceException. Dzieje się tak w czasie wykonywania, gdy null element jest wyłuszony; oznacza to, że gdy zmienna jest obliczana w czasie wykonywania, ale zmienna odwołuje się do null. Ten wyjątek jest zdecydowanie najczęściej występującym wyjątkiem w ekosystemie platformy .NET. Twórca null, Sir Tony Hoare, nazywa się null "błędem miliardów dolarów".

W poniższym przykładzie zmienna FooBar jest przypisywana do null zmiennej i natychmiast wyłuszczana, co powoduje wyświetlenie problemu:

// Declare variable and assign it as null.
FooBar fooBar = null;

// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();

// The FooBar type definition.
record FooBar(int Id, string Name);

Problem staje się znacznie trudniejszy do wykrycia jako deweloper, gdy aplikacje rosną w rozmiarze i złożoności. Wykrycie potencjalnych błędów, takich jak to, jest zadaniem narzędzi, a kompilator języka C# jest tutaj, aby pomóc.

Definiowanie bezpieczeństwa o wartości null

Termin Bezpieczeństwo wartości null definiuje zestaw funkcji specyficznych dla typów dopuszczających wartość null, które pomagają zmniejszyć liczbę możliwych NullReferenceException wystąpień.

Biorąc pod uwagę poprzedni FooBar przykład, można uniknąć NullReferenceException tego, sprawdzając, czy zmienna fooBar była null przed wyłuszeniem go:

// Declare variable and assign it as null.
FooBar fooBar = null;

// Check for null
if (fooBar is not null)
{
    _ = fooBar.ToString();
}

// The FooBar type definition for example.
record FooBar(int Id, string Name);

Aby ułatwić identyfikowanie takich scenariuszy, kompilator może wywnioskować intencję kodu i wymusić żądane zachowanie. Jest to jednak tylko wtedy, gdy jest włączony kontekst dopuszczalny do wartości null. Przed omówieniem kontekstu dopuszczalnego wartości null opiszmy możliwe typy dopuszczane do wartości null.

Typy dopuszczające wartości null

Przed C# 2.0 tylko typy odwołań były dopuszczane do wartości null. Typy wartości, takie jak int lub DateTime nie mogą być null. Jeśli te typy są inicjowane bez wartości, wracają do ich default wartości. W przypadku intelementu , jest 0to . W przypadku elementu DateTimejest to DateTime.MinValue.

Typy odwołań tworzone bez wartości początkowych działają inaczej. Wartość default dla wszystkich typów odwołań to null.

Rozważmy następujący fragment kodu w języku C#:

string first;                  // first is null
string second = string.Empty   // second is not null, instead it's an empty string ""
int third;                     // third is 0 because int is a value type
DateTime date;                 // date is DateTime.MinValue

W powyższym przykładzie:

  • first jest null spowodowane tym, że typ string odwołania został zadeklarowany, ale nie wykonano przypisania.
  • second jest przypisywany string.Empty po zadeklarowaniu. Obiekt nigdy nie miał null przypisania.
  • third mimo 0 braku przypisania. Jest to struct (typ wartości) i ma default wartość 0.
  • date jest niezainicjowany, ale jego default wartość to System.DateTime.MinValue.

Począwszy od języka C# 2.0, można zdefiniować typy wartości dopuszczalnych wartości null przy użyciu ( Nullable<T> lub T? w skrócie). Dzięki temu typy wartości mogą mieć wartość null. Rozważmy następujący fragment kodu w języku C#:

int? first;            // first is implicitly null (uninitialized)
int? second = null;    // second is explicitly null
int? third = default;  // third is null as the default value for Nullable<Int32> is null
int? fourth = new();    // fourth is 0, since new calls the nullable constructor

W powyższym przykładzie:

  • first Jest to null spowodowane tym, że typ wartości dopuszczanej do wartości null jest niezainicjowany.
  • second jest przypisywany null po zadeklarowaniu.
  • thirdparametr jest null wartością parametru default Nullable<int> .null
  • fourth jest 0 jak new() wyrażenie wywołuje Nullable<int> konstruktor i int jest 0 domyślnie.

Język C# 8.0 wprowadził typy odwołań dopuszczające wartości null, w których można wyrazić intencję, że typ odwołania może być null lub jest zawsze innynull niż. Możesz myśleć: "Myślałem, że wszystkie typy odwołań są dopuszczane do wartości null!" Nie masz nic złego i są. Ta funkcja umożliwia wyrażenie intencji, którą następnie kompilator próbuje wymusić. Ta sama T? składnia wyraża, że typ odwołania ma mieć wartość null.

Rozważmy następujący fragment kodu w języku C#:

#nullable enable

string first = string.Empty;
string second;
string? third;

Biorąc pod uwagę powyższy przykład, kompilator wywnioskuje intencję w następujący sposób:

  • firstnigdy nie null jest tak, jak na pewno jest przypisany.
  • secondnigdy nie powinna być nullwartością , mimo że początkowo jest nullto . Ocena second przed przypisaniem wartości powoduje wyświetlenie ostrzeżenia kompilatora, ponieważ jest niezainicjowane.
  • thirdmoże to być null. Na przykład może wskazywać element System.String, ale może wskazywać wartość null. Każda z tych odmian jest akceptowalna. Kompilator pomaga, ostrzegając, jeśli wyłuszczyć third bez uprzedniego sprawdzenia, czy nie ma wartości null.

Ważne

Aby można było używać funkcji typów odwołań dopuszczanych do wartości null, jak pokazano powyżej, musi znajdować się w kontekście dopuszczanym do wartości null. Jest to szczegółowo opisane w następnej sekcji.

Kontekst dopuszczany do wartości null

Konteksty dopuszczane do wartości null umożliwiają precyzyjną kontrolę sposobu interpretowania zmiennych typu odwołania przez kompilator. Istnieją cztery możliwe konteksty dopuszczane do wartości null:

  • disable: kompilator zachowuje się podobnie do języka C# 7.3 i starszych.
  • enable: Kompilator włącza całą analizę odwołania o wartości null i wszystkie funkcje języka.
  • warnings: Kompilator wykonuje całą analizę null i emituje ostrzeżenia, gdy kod może wyłuszczyć wartość null.
  • annotations: Kompilator nie wykonuje analizy wartości null ani nie emituje ostrzeżeń, gdy kod może wyłuszczyć wyjątek null, ale nadal można dodawać adnotacje do kodu przy użyciu typów ? odwołań dopuszczających wartość null i operatorów forgiving null (!).

Ten moduł ma zakres do disable kontekstów z możliwością wartości null lub enable z możliwością wartości null. Aby uzyskać więcej informacji, zapoznaj się z tematem Typy referencyjne dopuszczane do wartości Null: Konteksty dopuszczane do wartości null.

Włączanie typów odwołań dopuszczanych do wartości null

W pliku projektu C# (csproj) dodaj węzeł podrzędny <Nullable> do <Project> elementu (lub dołącz do istniejącego <PropertyGroup>elementu ). Spowoduje to zastosowanie enable kontekstu dopuszczanego do wartości null do całego projektu.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <!-- Omitted for brevity -->

</Project>

Alternatywnie można ograniczyć zakres kontekstu dopuszczalnego do wartości null do pliku C# przy użyciu dyrektywy kompilatora.

#nullable enable

Poprzednia dyrektywa kompilatora języka C# jest funkcjonalnie równoważna konfiguracji projektu, ale ma zakres do pliku, w którym się znajduje. Aby uzyskać więcej informacji, zobacz Typy referencyjne dopuszczane do wartości null: konteksty dopuszczane do wartości null (docs)

Ważne

Kontekst dopuszczalny do wartości null jest domyślnie włączony w pliku csproj we wszystkich szablonach projektów języka C#, począwszy od platformy .NET 6.0 i nowszych.

Po włączeniu kontekstu dopuszczalnego do wartości null zostaną wyświetlone nowe ostrzeżenia. Rozważmy poprzedni FooBar przykład, który zawiera dwa ostrzeżenia podczas analizowania w kontekście dopuszczalnym do wartości null:

  1. Wiersz FooBar fooBar = null; zawiera ostrzeżenie dotyczące null przypisania: Ostrzeżenie C# CS8600: Konwertowanie literału null lub możliwej wartości null na typ inny niż null.

    Zrzut ekranu przedstawiający ostrzeżenie C# CS8600: Konwertowanie literału null lub możliwej wartości null na typ niepusty.

  2. Wiersz _ = fooBar.ToString(); zawiera również ostrzeżenie. Tym razem kompilator obawia się, że fooBar może mieć wartość null: Ostrzeżenie C# CS8602: Dereference możliwego odwołania o wartości null.

    Zrzut ekranu przedstawiający ostrzeżenie C# CS8602: wyłusczenie możliwego odwołania o wartości null.

Ważne

Nie ma gwarantowanego bezpieczeństwa o wartości null, nawet jeśli reagujesz i eliminujesz wszystkie ostrzeżenia. Istnieją pewne ograniczone scenariusze, które przejdą analizę kompilatora, ale spowodują utworzenie środowiska uruchomieniowego NullReferenceException.

Podsumowanie

W tej lekcji przedstawiono sposób włączania kontekstu dopuszczalnego do wartości null w języku C#, aby chronić element przed elementem NullReferenceException. W następnej lekcji dowiesz się więcej na temat jawnego wyrażania intencji w kontekście dopuszczającym wartość null.