Typy referencyjne dopuszczane do wartości null (odwołanie w C#)

Uwaga

W tym artykule opisano typy referencyjne dopuszczane do wartości null. Można również zadeklarować typy wartości dopuszczanych do wartości null.

Użyj typów odwołań dopuszczających wartość null w kodzie, który znajduje się w kontekście obsługującym wartość null. Typy referencyjne dopuszczające wartość null, ostrzeżenia analizy statycznej o wartości null i operator forgiving o wartości null są opcjonalnymi funkcjami językowymi. Wszystkie są domyślnie wyłączone. Kontekst dopuszczalny do wartości null można kontrolować na poziomie projektu przy użyciu ustawień kompilacji lub w kodzie przy użyciu pragmas.

Dokumentacja języka C# zawiera ostatnio wydaną wersję języka C#. Zawiera również początkową dokumentację funkcji w publicznej wersji zapoznawczej nadchodzącej wersji językowej.

Dokumentacja identyfikuje dowolną funkcję po raz pierwszy wprowadzoną w ostatnich trzech wersjach języka lub w bieżącej publicznej wersji zapoznawczej.

Wskazówka

Aby dowiedzieć się, kiedy funkcja została po raz pierwszy wprowadzona w języku C#, zapoznaj się z artykułem dotyczącym historii wersji języka C#.

Ważne

Wszystkie szablony projektów włączają kontekst dopuszczalności wartości null dla projektu. Projekty utworzone przy użyciu wcześniejszych szablonów nie zawierają tego elementu, a te funkcje są wyłączone, chyba że włączysz je w pliku projektu lub użyj pragmas.

W kontekście obsługującym wartość null:

  • Musisz zainicjować zmienną typu T odwołania o wartości innej niż null i nigdy nie można przypisać wartości, która może mieć wartość null.
  • Można zainicjować zmienną typu T? odwołania za pomocą null metody lub przypisać nullją , ale przed wyłuszczeniem należy ją null sprawdzić.
  • Po zastosowaniu operatora forgiving o wartości null do zmiennej m typu T?, jak w , m!zmienna jest uważana za inną niż null.

Kompilator wymusza różnice między typem odwołania nienależącym do wartości null a typem TT? odwołania dopuszczającym wartość null przy użyciu powyższych reguł. Zmienna typu T i zmienna typu T? są tym samym typem platformy .NET. W poniższym przykładzie zadeklarowany jest ciąg bez wartości null i ciąg dopuszczający wartość null, a następnie używa operatora forgiving o wartości null do przypisania wartości do ciągu, który nie może mieć wartości null:

string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness

notNull Zmienne i nullable oba używają String typu. Ponieważ typy niepuste i dopuszczane do wartości null używają tego samego typu, nie można użyć typu odwołania dopuszczalnego wartości null w kilku lokalizacjach. Ogólnie rzecz biorąc, nie można użyć typu odwołania dopuszczanego do wartości null jako klasy bazowej ani zaimplementowanego interfejsu. Nie można użyć typu odwołania dopuszczanego do wartości null w żadnym wyrażeniu tworzenia obiektu ani testowania typu. Nie można użyć typu odwołania dopuszczanego do wartości null jako typu wyrażenia dostępu do składowej. W poniższych przykładach przedstawiono te konstrukcje:

public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
    if (thing is string? nullableString) // not allowed
        Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
    Console.WriteLine("error");
}

Odwołania dopuszczane do wartości null i analiza statyczna

Przykłady w poprzedniej sekcji ilustrują charakter typów odwołań dopuszczanych do wartości null. Typy referencyjne dopuszczane do wartości null nie są nowymi typami klas, ale raczej adnotacjami w istniejących typach referencyjnych. Kompilator używa tych adnotacji, aby ułatwić znalezienie potencjalnych błędów odwołania o wartości null w kodzie. Nie ma różnicy w czasie wykonywania między typem odwołania bez wartości null a typem odwołania dopuszczanym do wartości null. Kompilator nie dodaje żadnego sprawdzania środowiska uruchomieniowego pod kątem typów odwołań bez wartości null. Korzyści są w analizie czasu kompilacji. Kompilator generuje ostrzeżenia, które ułatwiają znajdowanie i naprawianie potencjalnych błędów null w kodzie. Zadeklarujesz intencję, a kompilator ostrzega Cię, gdy kod narusza ten zamiar.

Ważne

Adnotacje referencyjne dopuszczane do wartości null nie wprowadzają zmian zachowania, ale inne biblioteki mogą używać odbicia do tworzenia różnych zachowań środowiska uruchomieniowego dla typów odwołań dopuszczanych do wartości null i innych niż null. W szczególności program Entity Framework Core odczytuje atrybuty nullable. Interpretuje odwołanie z możliwością przyjęcia wartości null jako wartość opcjonalną i odwołanie niedopuszczalne do wartości null jako wymaganą wartość.

W kontekście z obsługą wartości null kompilator wykonuje analizę statyczną zmiennych dowolnego typu odwołania, zarówno dopuszczaną do wartości null, jak i niepustą. Kompilator śledzi stan null każdej zmiennej referencyjnej jako nie null lub być może null. Domyślny stan odwołania niezwiązanego z wartością null nie jest zerowy. Domyślnym stanem odwołania dopuszczanego do wartości null jest być może wartość null.

Typy odwołań bez wartości null powinny być zawsze bezpieczne do wyłudzenia, ponieważ ich stan null nie ma wartości null. Aby wymusić daną regułę, kompilator wystawia ostrzeżenia, jeśli typ odwołania niezwiązany z wartością null nie jest inicjowany do wartości innej niż null. Należy przypisać zmienne lokalne, w których je deklarujesz. Każde pole musi mieć przypisaną wartość niepustą w inicjatorze pola lub każdym konstruktorze. Kompilator zgłasza ostrzeżenia, gdy odwołanie niezwiązane z wartością null jest przypisane do odwołania, którego stan może ma wartość null. Ogólnie rzecz biorąc, odwołanie nie dopuszczające wartości null nie ma wartości null i podczas wyłudywania tych zmiennych nie są wyświetlane żadne ostrzeżenia.

Uwaga

Jeśli przypiszesz wyrażenie może mieć wartość null do typu odwołania innego niż null, kompilator generuje ostrzeżenie. Kompilator generuje następnie ostrzeżenia dla tej zmiennej, dopóki nie zostanie przypisane do wyrażenia innego niż null .

Można zainicjować lub przypisać null do typów odwołań dopuszczanych do wartości null. W związku z tym analiza statyczna musi określić, że zmienna nie ma wartości null , zanim zostanie wyłuszona. Jeśli odwołanie dopuszczające wartość null jest określane jako możliwe do wartości null, przypisanie go do zmiennej referencyjnej bez wartości null spowoduje wygenerowanie ostrzeżenia kompilatora. W poniższej klasie przedstawiono przykłady tych ostrzeżeń:

public class ProductDescription
{
    private string shortDescription;
    private string? detailedDescription;

    public ProductDescription() // Warning! shortDescription not initialized.
    {
    }

    public ProductDescription(string productDescription) =>
        this.shortDescription = productDescription;

    public void SetDescriptions(string productDescription, string? details=null)
    {
        shortDescription = productDescription;
        detailedDescription = details;
    }

    public string GetDescription()
    {
        if (detailedDescription.Length == 0) // Warning! dereference possible null
        {
            return shortDescription;
        }
        else
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
    }

    public string FullDescription()
    {
        if (detailedDescription == null)
        {
            return shortDescription;
        }
        else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
        return shortDescription;
    }
}

Poniższy fragment kodu pokazuje, gdzie kompilator emituje ostrzeżenia podczas korzystania z tej klasy:

string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.

string description = "widget";
var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

W poprzednich przykładach pokazano, jak analiza statyczna kompilatora określa stan null zmiennych referencyjnych. Kompilator stosuje reguły języka do sprawdzania wartości null i przypisań w celu informowania o analizie. Kompilator nie może podjąć założeń dotyczących semantyki metod lub właściwości. Jeśli wywołasz metody, które wykonują kontrole wartości null, kompilator nie może wiedzieć, że te metody wpływają na stan null zmiennej. Możesz dodać atrybuty do interfejsów API, aby poinformować kompilator o semantyce argumentów i zwracanych wartości. Wiele typowych interfejsów API w bibliotekach .NET ma te atrybuty. Na przykład kompilator poprawnie interpretuje IsNullOrEmpty jako sprawdzanie wartości null. Aby uzyskać więcej informacji na temat atrybutów, które mają zastosowanie do analizy statycznej stanu null, zobacz artykuł dotyczący atrybutów dopuszczających wartość null.

Kontekst dopuszczany do wartości null

Kontekst dopuszczający wartość null określa, w jaki sposób kompilator obsługuje adnotacje typu odwołania dopuszczające wartość null i jakie ostrzeżenia są generowane podczas statycznej analizy stanu null. Kontekst dopuszczający wartość null zawiera dwie flagi: ustawienie adnotacji oraz ustawienie ostrzeżenia .

Zarówno ustawienia adnotacji , jak i ustawienia ostrzeżeń są domyślnie wyłączone dla istniejących projektów. Począwszy od .NET 6 (C# 10), obie flagi są domyślnie włączone dla projektów new. Powodem istnienia dwóch odrębnych flag dla kontekstu dopuszczającego wartość null jest ułatwienie migracji dużych projektów, które powstały przed wprowadzeniem typów odniesienia dopuszczających wartość null.

W przypadku małych projektów można włączyć typy odwołań dopuszczające wartości null, naprawić ostrzeżenia i kontynuować. Jednak w przypadku większych projektów i rozwiązań wieloprojektowych proces ten może wygenerować dużą liczbę ostrzeżeń. Należy użyć dyrektyw preprocesora, aby włączać typy odwołań dopuszczające wartość null dla każdego pliku, gdy rozpoczynasz korzystanie z tych typów. Nowe funkcje chroniące przed rzuceniem System.NullReferenceException mogą mieć destrukcyjny wpływ po włączeniu w istniejącej bazie kodu.

  • Wszystkie jawnie wpisane zmienne referencyjne są interpretowane jako typy referencyjne, które nie mogą być dopuszczane do wartości null.
  • Znaczenie class ograniczenia w rodzajach ogólnych zmieniło się na oznaczające typ odwołania niezwiązany z wartością null.
  • Nowe ostrzeżenia są generowane z powodu tych nowych reguł.

Kontekst adnotacji nullable określa zachowanie kompilatora. Istnieją cztery kombinacje ustawień kontekstu dopuszczających wartości null dla .

  • oboje wyłączone: kod nie uwzględnia wartości null. Wyłącz pasuje do zachowania sprzed włączenia typów odwołań dopuszczanych do wartości null, z wyjątkiem tego, że nowa składnia powoduje wygenerowanie ostrzeżeń zamiast błędów.
    • Ostrzeżenia dopuszczające wartość null są wyłączone.
    • Wszystkie zmienne typu odwołania są typami referencyjnymi dopuszczającymi wartość null.
    • Użycie sufiksu ? do zadeklarowania typu odwołania dopuszczającego wartość null powoduje ostrzeżenie.
    • Możesz użyć operatora null-forgiving, !, ale nie ma żadnego efektu.
  • oba włączone: kompilator włącza analizę odwołań zerowych i wszystkie funkcje językowe.
    • Wszystkie nowe ostrzeżenia dopuszczające wartość null są włączone.
    • Można użyć sufiksu ?, aby zadeklarować typ odwołania, który może przyjmować wartość null.
    • Zmienne typu odwołania bez sufiksu ? są typami referencyjnymi nie dopuszczającymi wartości null.
    • Operator ignorujący wartość null pomija ostrzeżenia dotyczące możliwego odwołania się do null.
  • ostrzeżenie włączone: kompilator dokonuje analizy wartości null i emituje ostrzeżenia, gdy kod może odwołać się do null.
    • Wszystkie nowe ostrzeżenia dopuszczające wartość null są włączone.
    • Użycie sufiksu ? do zadeklarowania typu odwołania dopuszczającego wartość null powoduje ostrzeżenie.
    • Wszystkie zmienne typu odwołania mogą mieć wartość null. Jednak składowe mają stan null jako not-null na początku nawiasu klamrowego wszystkich metod, chyba że są zadeklarowane z sufiksem ?.
    • Możesz użyć operatora forgiving o wartości null, !.
  • włączone adnotacje: kompilator nie wyświetla ostrzeżeń, gdy kod może odwołać się do null, lub podczas przypisywania wyrażenia, które może mieć wartość null, do zmiennej nienullowej.
    • Wszystkie nowe ostrzeżenia dopuszczające wartość null są wyłączone.
    • Można użyć sufiksu ?, aby zadeklarować typ odwołania, który może przyjmować wartość null.
    • Zmienne typu odwołania bez sufiksu ? są typami referencyjnymi nie dopuszczającymi wartości null.
    • Możesz użyć operatora null-forgiving, !, ale nie ma żadnego efektu.

Kontekst adnotacji dopuszczający wartość null i kontekst ostrzeżenia dopuszczający wartość null dla projektu można ustawić przy użyciu <Nullable> elementu w pliku csproj . Ten element konfiguruje sposób, w jaki kompilator interpretuje wartość null typów i jakie ostrzeżenia emituje. W poniższej tabeli przedstawiono dozwolone wartości i podsumowano określone konteksty.

Context Ostrzeżenia o dereferencji Ostrzeżenia dotyczące przypisania Typy odwołań ? przyrostek ! Operator
disable Wyłączony Wyłączony Wszystkie mogą mieć wartość null Tworzy ostrzeżenie Nie ma żadnego efektu
enable Enabled Enabled Nie może mieć wartości null, chyba że zadeklarowane za pomocą ? Deklaruje typ dopuszczający wartości null Pomija ostrzeżenia dotyczące możliwego null przypisania
warnings Enabled Nie dotyczy Wszystkie są dopuszczane do wartości null, ale elementy członkowskie są uznawane za niepuste podczas otwierania nawiasu klamrowego metod Tworzy ostrzeżenie Pomija ostrzeżenia dotyczące możliwego null przypisania
annotations Wyłączony Wyłączony Nie może mieć wartości null, chyba że zadeklarowane za pomocą ? Deklaruje typ dopuszczający wartości null Nie ma żadnego efektu

Zmienne typu odwołania w kodzie skompilowanym w kontekście wyłączonym nieświadome wartości null. Można przypisać literał lub zmienną null do zmiennej, która jest nieświadoma nullowalności. Jednak domyślny stan zmiennej o wartości null nie jest zerowy.

Wybierz ustawienie, które najlepiej pasuje do projektu:

  • Wybierz opcję wyłącz dla starszych projektów, których nie chcesz aktualizować na podstawie diagnostyki ani nowych funkcji.
  • Wybierz ostrzeżenia, aby określić, gdzie kod może zgłaszać System.NullReferenceException. Ostrzeżenia te można rozwiązać przed zmodyfikowaniem kodu, aby włączyć typy referencyjne niezezwalające na wartość null.
  • Wybierz adnotacje , aby wyrazić intencję projektu przed włączeniem ostrzeżeń.
  • Wybierz Włącz dla nowych projektów i aktywnych projektów, w których chcesz zabezpieczyć się przed wyjątkami odwołań pustych.

Przykład:

<Nullable>enable</Nullable>

Możesz również użyć dyrektyw, aby ustawić te same flagi w dowolnym miejscu w kodzie źródłowym. Te dyrektywy są najbardziej przydatne podczas migrowania dużej bazy kodu.

  • #nullable enable: ustawia flagi adnotacji i ostrzeżeń, aby włączyć.
  • #nullable disable: ustawia flagi adnotacji i ostrzeżeń, aby wyłączyć.
  • #nullable restore: przywraca flagę adnotacji i flagę ostrzeżenia do ustawień projektu.
  • #nullable disable warnings: ustawia flagę ostrzeżenia, aby ją wyłączyć.
  • #nullable enable warnings: ustawia flagę ostrzeżenia, aby ją włączyć.
  • #nullable restore warnings: przywraca flagę ostrzeżenia do ustawień projektu.
  • #nullable disable annotations: ustawia flagę adnotacji, aby ją wyłączyć.
  • #nullable enable annotations: ustawia flagę adnotacji, aby ją włączyć.
  • #nullable restore annotations: przywraca flagę adnotacji do ustawień projektu.

W przypadku dowolnego wiersza kodu można ustawić dowolną z następujących kombinacji:

Flaga ostrzeżenia Flaga adnotacji Użycie
domyślna wartość projektu domyślna wartość projektu Wartość domyślna
włączyć wyłączyć Naprawianie ostrzeżeń dotyczących analizy
włączyć domyślna wartość projektu Naprawianie ostrzeżeń dotyczących analizy
domyślna wartość projektu włączyć Dodaj adnotacje typu
włączyć włączyć Kod został już zmigrowany
wyłączyć włączyć Dodawanie adnotacji do kodu przed naprawieniem ostrzeżeń
wyłączyć wyłączyć Dodawanie starszego kodu do zmigrowanego projektu
domyślna wartość projektu wyłączyć Rzadko
wyłączyć domyślna wartość projektu Rzadko

Te dziewięć kombinacji zapewnia precyzyjną kontrolę nad diagnostyką emitowaną przez kompilator kodu. Możesz włączyć więcej funkcji w dowolnym obszarze, który aktualizujesz, bez wyświetlania większej liczby ostrzeżeń, które nie są jeszcze gotowe do rozwiązania.

Ważne

Globalny kontekst dopuszczania wartości null nie ma zastosowania do wygenerowanych plików kodu. W ramach każdej strategii kontekst dopuszczalny do wartości null jest wyłączony dla dowolnego pliku źródłowego oznaczonego jako wygenerowany. Ten warunek oznacza, że kompilator nie dodawać adnotacji do żadnych interfejsów API w wygenerowanych plikach. Kompilator nie generuje ostrzeżeń o wartości null dla wygenerowanych plików. Plik jest oznaczony jako wygenerowany na dowolny z następujących czterech sposobów:

  1. W pliku .editorconfig określ generated_code = true w sekcji, która ma zastosowanie do tego pliku.
  2. Umieść <auto-generated> lub <auto-generated/> w komentarzu w górnej części pliku. Może on znajdować się w dowolnym wierszu w tym komentarzu, ale blok komentarza musi być pierwszym elementem w pliku.
  3. Rozpocznij nazwę pliku od TemporaryGeneratedFile_
  4. Zakończ nazwę pliku jako .designer.cs, .generated.cs, .g.cs lub .g.i.cs.

Generatory mogą wyrazić zgodę przy użyciu #nullable dyrektywy preprocesora.

Domyślnie flagi adnotacji dopuszczające wartość null i ostrzeżenia są wyłączone. To ustawienie domyślne oznacza, że istniejący kod jest kompilowany bez zmian i bez generowania nowych ostrzeżeń. Począwszy od .NET 6, nowe projekty obejmują element <Nullable>enable</Nullable> we wszystkich szablonach projektów, ustawiając te flagi na enabled.

Te opcje zapewniają dwie odrębne strategie aktualizacji istniejącej bazy kodu w celu używania typów odwołań dopuszczających wartości null.

Ustawianie kontekstu dopuszczalnego do wartości null

Kontekst dopuszczalny do wartości null można kontrolować na dwa sposoby. Na poziomie projektu dodaj <Nullable>enable</Nullable> ustawienie projektu. W jednym pliku źródłowym języka C# dodaj #nullable enable pragma, aby włączyć kontekst dopuszczalny do wartości null. Aby uzyskać więcej informacji, zobacz ustawianie strategii dopuszczanej do wartości null. Przed platformą .NET 6 nowe projekty używają wartości domyślnej, <Nullable>disable</Nullable>. Począwszy od platformy .NET 6, nowe projekty zawierają <Nullable>enable</Nullable> element w pliku projektu.

Typy ogólne

Jeśli używasz parametru typu , Tjako odpowiednik dopuszczający wartość null, rzeczywisty argument typu określa, T?jak ? jest interpretowany. Rozważ następującą deklarację ogólną:

public class Box<T>
{
    public T Contents { get; set; }
}

Ponieważ parametr typu może oznaczać typ odwołania lub typ wartości, znaczenie parametru zależy od argumentu T? typu, który dostarcza obiekt wywołujący. W poniższych regułach opisano, co T? rozwiązuje problem, gdy T nie ma ograniczeń:

  • Argument typu jest typem referencyjnym, który nie może zawierać wartości null. W przypadku Box<string>parametru T jest string i T? to string?— odpowiadający typ odwołania dopuszczalny do wartości null.
  • Argument typu jest typem wartości. W przypadku Box<int>parametru T i jest intT? również int— ten sam typ wartości. Adnotacja nie ma wpływu na typy wartości, chyba że parametr typu ma struct ograniczenie, w którym przypadku T? oznacza Nullable<T> (int?).
  • Argument typu jest już dopuszczany do wartości null. W przypadku Box<string?>parametru T jest string?T? i nadal jest .string? Nie otrzymujesz typu "podwójnie dopuszczania wartości null".

Ograniczenia ograniczają, które argumenty typu są dozwolone. Umożliwiają one również przyczynę kompilatora dotyczące T sposobu użycia:

  • where T : class wymaga typu odwołania bez wartości null. Box<string> jest dozwolona; Box<string?> polecenie generuje ostrzeżenie.
  • where T : class? zezwala na typ odwołania dopuszczany do wartości null lub nienależące do wartości null. Zarówno, jak Box<string> i Box<string?> są dozwolone.
  • where T : struct wymaga typu wartości innej niż null. Box<int> jest dozwolona; Box<int?> Nie. Z tym ograniczeniem T? wewnątrz ogólnych środków Nullable<T>— dla Box<int>, T? jest int?.
  • where T : notnull wymaga niepustego odwołania lub typu wartości. Box<string> i Box<int> są dozwolone; Box<string?> tworzy ostrzeżenie.
  • where T : BaseType wymaga typu odwołania innego niż null, który pochodzi z BaseTypeklasy . Dołącz ? (where T : BaseType?), aby zezwolić również na typy pochodne dopuszczane do wartości null.

Ograniczenia pomagają kompilatorowi w sposobie użycia parametru typu ogólnego:

public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
    foreach (T item in source)
    {
        return item;
    }
    return default;
}

public static void RequireNotNull<T>(T value) where T : notnull
{
    ArgumentNullException.ThrowIfNull(value);
}

public static void Generics()
{
    string? first = FirstOrDefault<string>([]);
    Console.WriteLine(first ?? "<empty>");

    RequireNotNull("not null");
}

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz sekcję Typy referencyjne dopuszczane do wartości nullspecyfikacji języka C#.

Zobacz też