Typy referencyjne dopuszczające wartość null
Typy referencyjne dopuszczające wartość null to zbiór właściwości, które minimalizują prawdopodobieństwo, że kod spowoduje zgłoszenie przez środowisko uruchomieniowe System.NullReferenceException. Trzy funkcje, które pomagają uniknąć tych wyjątków, w tym możliwość jawnego oznaczania typu odwołania jako nullable:
- Ulepszona analiza przepływu statycznego, która określa, czy zmienna może być
null
przed wyłuszczeniem jej. - Atrybuty, które adnotują interfejsy API, aby analiza przepływu określała stan null.
- Adnotacje zmiennych używane przez deweloperów do jawnego deklarowania zamierzonego stanu null dla zmiennej.
Kompilator śledzi stan zerowy każdego wyrażenia w kodzie w czasie kompilacji. Stan zerowy ma jedną z dwóch wartości:
-
not-null: wyrażenie jest znane jako not-
null
. -
może mieć wartość null: wyrażenie może mieć wartość
null
.
Adnotacje zmiennych określają nullowalność zmiennej typu odwołania:
-
non-nullowalne: jeśli przypiszesz
null
wartość null lub może być null wyrażenie do zmiennej, kompilator wyświetla ostrzeżenie. Zmienne, które nie mogą mieć wartości null, mają domyślny stan nie-null. -
nullable: możesz przypisać
null
wartość lub wyrażenie z możliwością wartości null do zmiennej. Gdy stan zmiennej to może być null, kompilator wyświetla ostrzeżenie, jeśli dokonasz dereferencji zmiennej. Domyślny stan null dla zmiennej to może mieć wartość null.
W pozostałej części tego artykułu opisano, jak działają te trzy obszary funkcjonalne, aby generować ostrzeżenia, gdy kod może dereferencjonować jakąś wartość null
. Wyłuszczenie zmiennej oznacza dostęp do jednego z jej elementów przy użyciu operatora .
(kropka), jak pokazano w poniższym przykładzie.
string message = "Hello, World!";
int length = message.Length; // dereferencing "message"
Gdy dokonujesz dereferencji zmiennej, której wartość to null
, środowisko uruchomieniowe zgłasza System.NullReferenceException.
Podobnie ostrzeżenia mogą być generowane, gdy []
notacja jest używana do uzyskiwania dostępu do elementu członkowskiego obiektu, gdy obiekt ma wartość null
:
using System;
public class Collection<T>
{
private T[] array = new T[100];
public T this[int index]
{
get => array[index];
set => array[index] = value;
}
}
public static void Main()
{
Collection<int> c = default;
c[10] = 1; // CS8602: Possible dereference of null
}
Dowiesz się więcej o:
- Analiza stanu null kompilatora: jak kompilator określa, czy wyrażenie nie ma wartości null, czy może ma wartość null.
- Atrybuty stosowane do interfejsów API, które zapewniają więcej kontekstu dla analizy stanu null kompilatora.
- Adnotacje zmiennych dopuszczających wartość null, które dostarczają informacji o przeznaczeniu zmiennych. Adnotacje są przydatne w przypadku pól, parametrów i zwracanych wartości w celu ustawienia domyślnego stanu null.
- Reguły regulujące argumenty typów ogólnych. Dodano nowe ograniczenia, ponieważ parametry typu mogą być typami referencyjnymi lub typami wartości. Sufiks
?
jest implementowany inaczej dla typów wartości nulowalnych i typów odwołań nulowalnych. - Kontekst dopuszczany do wartości null ułatwia migrowanie dużych projektów. Podczas migracji można włączyć ostrzeżenia i adnotacje w kontekście dopuszczającym wartości null w częściach aplikacji. Po usunięciu większej liczby ostrzeżeń można włączyć oba ustawienia dla całego projektu.
Na koniec poznasz znane pułapki analizy stanu null w struct
typach i tablicach.
Możesz również zapoznać się z tymi pojęciami w naszym module Learn dotyczącym bezpieczeństwa dopuszczanego do wartości null w języku C#.
Analiza stanu null
Analiza stanu null śledzi stan null odwołań. Wyrażenie nie ma wartości null lub może mieć wartość null. Kompilator określa, że zmienna nie ma wartości null na dwa sposoby:
- Zmienna została przypisana wartość, która jest znana jako nie null.
- Zmienna została sprawdzona względem
null
i nie została przypisana od tamtej pory.
Każda zmienna, której kompilator nie może określić jako nie będące wartością null, jest uważana za może być wartością null. Analiza udostępnia ostrzeżenia w sytuacjach, w których można przypadkowo odwołać się do wartości null
. Kompilator generuje ostrzeżenia na podstawie stanu null.
- Jeśli zmienna nie jest null, to tę zmienną można bezpiecznie wyłuskać.
- pl-PL: Gdy zmienna może być null, należy sprawdzić tę zmienną, aby upewnić się, że nie jest
null
przed jej wyłuszczeniem.
Rozważmy następujący przykład:
string? message = null;
// warning: dereference null.
Console.WriteLine($"The length of the message is {message.Length}");
var originalMessage = message;
message = "Hello, World!";
// No warning. Analysis determined "message" is not-null.
Console.WriteLine($"The length of the message is {message.Length}");
// warning!
Console.WriteLine(originalMessage.Length);
W poprzednim przykładzie kompilator określa, że message
może mieć wartość null, gdy pierwszy komunikat jest drukowany. Nie ma ostrzeżenia dla drugiego komunikatu. Ostatni wiersz kodu generuje ostrzeżenie, ponieważ originalMessage
może mieć wartość null. W poniższym przykładzie przedstawiono bardziej praktyczne zastosowanie przechodzenia drzewa węzłów do korzenia, z przetwarzaniem każdego węzła podczas przechodzenia.
void FindRoot(Node node, Action<Node> processNode)
{
for (var current = node; current != null; current = current.Parent)
{
processNode(current);
}
}
Poprzedni kod nie generuje żadnych ostrzeżeń dotyczących dereferencjonowania zmiennej current
. Analiza statyczna określa, że current
nigdy nie jest odwoływany, gdy może być null. Zmienna current
jest sprawdzana względem null
przed uzyskaniem dostępu do current.Parent
i przed przekazaniem current
do akcji ProcessNode
. W poprzednich przykładach pokazano, jak kompilator określa stan null dla zmiennych lokalnych podczas inicjowania, przypisywania lub porównywania z null
.
Analiza stanu null nie wnika w wywoływane metody. W efekcie pola inicjalizowane we wspólnej metodzie pomocniczej wywoływanej przez wszystkie konstruktory mogą wygenerować ostrzeżenie z następującym komunikatem:
Właściwość 'name' nieprzyjmująca wartości null musi zawierać wartość niezerową podczas kończenia działania konstruktora.
Te ostrzeżenia można rozwiązać na jeden z dwóch sposobów: łańcuch konstruktora lub atrybuty dopuszczające wartość null w metodzie pomocniczej. Poniższy kod przedstawia przykład każdego z nich. Klasa Person
używa wspólnego konstruktora wywoływanego przez wszystkie inne konstruktory. Klasa Student
ma metodę pomocnika z adnotacjami z atrybutem System.Diagnostics.CodeAnalysis.MemberNotNullAttribute :
using System.Diagnostics.CodeAnalysis;
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public Person() : this("John", "Doe") { }
}
public class Student : Person
{
public string Major { get; set; }
public Student(string firstName, string lastName, string major)
: base(firstName, lastName)
{
SetMajor(major);
}
public Student(string firstName, string lastName) :
base(firstName, lastName)
{
SetMajor();
}
public Student()
{
SetMajor();
}
[MemberNotNull(nameof(Major))]
private void SetMajor(string? major = default)
{
Major = major ?? "Undeclared";
}
}
Analiza stanu nullowalnego i ostrzeżenia wydawane przez kompilator pomagają unikać błędów programu poprzez odwołanie null
. Artykuł dotyczący rozpoznawania ostrzeżeń dopuszczających wartość null zawiera techniki poprawiania ostrzeżeń, które najprawdopodobniej są widoczne w kodzie. Diagnostyka utworzona na podstawie analizy stanu null jest tylko ostrzeżeniami.
Atrybuty podpisów interfejsu API
Analiza stanu null wymaga wskazówek ze strony deweloperów dla zrozumienia semantykę interfejsów API. Niektóre interfejsy API zapewniają kontrole wartości null i powinny zmienić stan null zmiennej z być może-null na not-null. Inne interfejsy API zwracają wyrażenia, które mają wartość nie-null lub może mieć wartość null, w zależności od stanu nullowości argumentów wejściowych. Rozważmy na przykład następujący kod, który wyświetla komunikat wielkimi literami.
void PrintMessageUpper(string? message)
{
if (!IsNull(message))
{
Console.WriteLine($"{DateTime.Now}: {message.ToUpper()}");
}
}
bool IsNull(string? s) => s == null;
Na podstawie inspekcji każdy deweloper rozważy ten kod jako bezpieczny i nie powinien generować ostrzeżeń. Ponieważ kompilator nie wie, że IsNull
zapewnia sprawdzanie wartości null, wyświetla ostrzeżenie w instrukcji message.ToUpper()
, biorąc pod uwagę, że message
to zmienna, która może mieć wartość null. Użyj atrybutu , NotNullWhen
aby naprawić to ostrzeżenie:
bool IsNull([NotNullWhen(false)] string? s) => s == null;
Ten atrybut informuje kompilator, że jeśli IsNull
zwraca false
, parametr s
nie jest zerowy. Kompilator zmienia stan nullmessage
na not-null wewnątrz bloku if (!IsNull(message)) {...}
. Nie są wyświetlane żadne ostrzeżenia.
Atrybuty dostarczają szczegółowe informacje o stanie null argumentów, wartościach zwracanych i członkach wystąpienia obiektu używanego do wywołania członka. Szczegółowe informacje na temat każdego atrybutu można znaleźć w artykule referencyjnym języka na temat atrybutów referencyjnych dopuszczanych do wartości null. Od platformy .NET 5 wszystkie interfejsy API środowiska uruchomieniowego platformy .NET są oznaczone adnotacjami. Możesz poprawić analizę statyczną, dodając adnotacje do interfejsów API w celu uzyskania informacji semantycznych o nullowym stanie argumentów i wartości zwracanych.
Adnotacje zmiennych dopasowanych do null
Analiza stanu null zapewnia niezawodną analizę zmiennych lokalnych. Kompilator potrzebuje więcej informacji na temat zmiennych członkowskich. Kompilator potrzebuje więcej informacji, aby ustawić stan pusty wszystkich pól przy nawiasie otwierającym członka. Dowolny z dostępnych konstruktorów może służyć do inicjowania obiektu. Jeśli pole członkowskie może kiedykolwiek zostać ustawione na null
, kompilator musi przyjąć, że jego stan null to może być null na początku każdej metody.
Używasz adnotacji, które mogą deklarować, czy zmienna jest typem odwołania dopuszczającym wartości null, czy typem odwołania niedopuszczającym wartości null. Te adnotacje przedstawiają ważne stwierdzenia dotyczące stanu null zmiennych:
-
Referencja nie powinna być nullem. Wartość domyślna zmiennej referencyjnej nie dopuszcza wartości null i jest nie-null. Kompilator wymusza zasady, które zapewniają, że można bezpiecznie odwołać się do tych zmiennych bez wcześniejszego sprawdzenia, czy nie mają wartości null.
- Zmienna musi zostać zainicjowana do wartości innej niż null.
- Zmienna nigdy nie może mieć przypisanej wartości
null
. Kompilator generuje ostrzeżenie, gdy kod przypisuje wyrażenie może o wartości null do zmiennej, która nie powinna mieć wartości null.
-
Odwołanie może być puste. Domyślny stan zmiennej referencyjnej dopuszczanej do wartości null to może mieć wartość null. Kompilator wymusza przestrzeganie reguł, aby upewnić się, że prawidłowo sprawdzasz odniesienie
null
.- Zmienna może zostać wyłuszona tylko wtedy, gdy kompilator może zagwarantować, że wartość nie wynosi
null
. - Te zmienne można zainicjować przy użyciu wartości domyślnej
null
i można przypisać wartośćnull
w innym kodzie. - Kompilator nie generuje ostrzeżeń, gdy kod przypisuje wyrażenie może mieć wartość null do zmiennej, która może mieć wartość null.
- Zmienna może zostać wyłuszona tylko wtedy, gdy kompilator może zagwarantować, że wartość nie wynosi
Każda niemozliwa do zanulowania zmienna referencyjna ma początkowy stan nie-null. Każda zmienna referencyjna dopuszczająca wartość null ma początkowy stan null jako może być null.
Typ odwołania dopuszczającego wartość null jest oznaczony przy użyciu tej samej składni co typy wartości dopuszczających wartość null: element ?
jest dołączany do typu zmiennej. Na przykład następująca deklaracja zmiennej reprezentuje zmienną ciągu dopuszczaną do wartości null, name
:
string? name;
Po włączeniu typów referencyjnych dopuszczających wartości null, każda zmienna, w której ?
nie jest dołączona do nazwy typu, jest nierozdzielnym typem referencyjnym. Obejmuje to wszystkie zmienne typu referencyjnego w istniejącym kodzie, gdy włączysz tę funkcję. Jednak wszelkie niejawnie wpisane zmienne lokalne (zadeklarowane przy użyciu var
) są nullowalnymi typami referencyjnymi. Jak pokazano w poprzednich sekcjach, analiza statyczna określa stan null zmiennych lokalnych, aby ustalić, czy może są nullem przed ich wyłuszczeniem.
Czasami należy zignorować ostrzeżenie, gdy wiadomo, że zmienna nie ma wartości null, ale kompilator określa, że jej stan null jest być może null. Używasz operatora tolerancji wartości null po nazwie zmiennej, aby wymusić, że stan null nie jest null. Jeśli na przykład wiesz, że zmienna name
nie jest null
, ale kompilator generuje ostrzeżenie, możesz napisać następujący kod, aby przesłonić analizę kompilatora.
name!.Length;
Typy referencyjne dopuszczające wartość null i typy wartości dopuszczających wartość null zapewniają podobną koncepcję semantyczną: Zmienna może reprezentować wartość lub obiekt, lub ta zmienna może być null
. Jednak typy referencyjne możliwe do przypisania wartości null i typy wartości możliwe do przypisania wartości null są implementowane inaczej: typy wartości możliwe do przypisania wartości null są implementowane przy użyciu System.Nullable<T>, a typy referencyjne możliwe do przypisania wartości null są implementowane przez atrybuty odczytywane przez kompilator. Na przykład string?
i string
są reprezentowane przez ten sam typ: System.String.
int?
Jednak i int
są reprezentowane odpowiednio przez System.Nullable<System.Int32>
i System.Int32.
Typy referencyjne dopuszczające wartości null to funkcjonalność na etapie kompilacji. To oznacza, że dzwoniący mogą ignorować ostrzeżenia i celowo używać null
jako argumentu do metody oczekującej referencji niemającej wartości null. Autorzy bibliotek powinni uwzględnić kontrole w czasie wykonywania, aby zapobiegać wartościom argumentów równym null. Preferowaną opcją jest ArgumentNullException.ThrowIfNull do sprawdzania, czy parametr jest równy null podczas działania programu. Ponadto zachowanie środowiska uruchomieniowego programu używającego adnotacji 'nullable' jest takie samo, jeśli wszystkie adnotacje 'nullable' (?
i !
) zostaną usunięte. Jedyne ich przeznaczenie to wyrażanie intencji projektu i dostarczanie informacji na potrzeby analizy stanu zerowego.
Ważne
Włączenie adnotacji dopuszczających wartość null może zmienić sposób, w jaki Entity Framework Core określa, czy element członkowski danych jest wymagany. Więcej szczegółów można znaleźć w artykule Dotyczący podstaw platformy Entity Framework Core: praca z referencyjnymi typami dopuszczającymi wartości null.
Typy ogólne
Typy ogólne wymagają szczegółowych reguł do obsługi T?
dla dowolnego parametru T
typu . Reguły są szczegółowe z konieczności ze względu na historię i różne implementacje typu wartości dopuszczającego wartość null oraz typu odwołania dopuszczającego wartość null.
Typy wartości dopuszczalne są implementowane za pomocą struktury System.Nullable<T>.
Typy referencyjne nullable są implementowane jako adnotacje typu, które dostarczają semantyczne reguły dla kompilatora.
- Jeśli argument typu dla
T
jest typem odwołania,T?
odwołuje się do odpowiadającego mu nullable reference type. Jeśli na przykładT
jeststring
, toT?
jeststring?
. - Jeśli argument typu dla
T
jest typem wartości,T?
odwołuje się do tego samego typu wartości,T
. Jeśli na przykładT
toint
,T?
jest równieżint
. - Jeśli argument typu dla
T
to typ referencyjny dopuszczający wartość null,T?
odwołuje się do tego samego typu referencyjnego dopuszczającego wartość null. Na przykład jeśliT
jeststring?
, toT?
jest takżestring?
. - Jeśli argument typu dla
T
jest typem wartości dopuszczającej null,T?
odwołuje się do tego samego typu wartości dopuszczającej null. Na przykład, jeśliT
jestint?
, toT?
jest równieżint?
.
W przypadku wartości zwracanych, T?
jest równoważne [MaybeNull]T
; dla wartości argumentów, T?
jest równoważne [AllowNull]T
. Aby uzyskać więcej informacji, zapoznaj się z artykułem na temat Atrybuty dla analizy stanu null w dokumentacji języka.
Możesz określić różne zachowanie przy użyciu ograniczeń:
- Ograniczenie
class
oznacza, żeT
musi być niepustym typem odwołania (na przykładstring
). Kompilator generuje ostrzeżenie, jeśli używasz referencji przystosowanej do null, takiej jakstring?
dlaT
. - Ograniczenie
class?
oznacza, żeT
musi być typem odwołania, innym niż null () lub typem odwołania dopuszczanym do wartości null (string
na przykładstring?
). Gdy parametr typu jest typem odwołania dopuszczającym wartość null, takim jakstring?
, wyrażenieT?
odnosi się do tego samego typu odwołania dopuszczającego wartość null, takiego jakstring?
. - Ograniczenie
notnull
oznacza, żeT
musi być typem referencyjnym, który nie może mieć wartości null lub typem wartościowym, który nie dopuszcza wartości null. Jeśli używasz typu odwołania dopuszczanego do wartości null lub typu wartości dopuszczanej do wartości null dla parametru typu, kompilator generuje ostrzeżenie. Ponadto, gdyT
jest typem wartości, zwracana wartość jest tym typem wartości, a nie odpowiadającym mu typem wartości dopuszczającym wartość null.
Te ograniczenia pomagają dostarczyć więcej informacji do kompilatora na temat tego, jak T
jest używany. Pomaga to, gdy deweloperzy wybierają typ dla T
, a także zapewnia lepszą analizę stanu null, kiedy używane jest wystąpienie typu ogólnego.
Kontekst dopuszczany do wartości null
Kontekst dopuszczający wartość null określa, jak obsługiwane są adnotacje typu odwołania dopuszczające wartość null oraz jakie ostrzeżenia są generowane przez statyczną analizę 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 platformy .NET 6 (C# 10), obie flagi są domyślnie włączone dla nowych projektów. 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 może to spowodować wygenerowanie dużej liczby 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 generuje 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 dopuszczanego do wartości null. - Zmienne typu referencyjnego bez sufiksu
?
są typami referencyjnymi, które nie mogą przyjąć wartości null. - Możesz użyć operatora ignorującego null,
!
, ale nie ma to żadnego efektu.
Kontekst adnotacji obsługującej wartość null i kontekst ostrzeżeń obsługujący wartość null można ustawić dla projektu, używając elementu <Nullable>
w pliku .csproj. Ten element konfiguruje sposób, w jaki kompilator interpretuje wartość null typów i jakie ostrzeżenia są emitowane. W poniższej tabeli przedstawiono dozwolone wartości i podsumowano określone konteksty.
Kontekst | 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 |
Włączony | Włączony | 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 |
Włączony | 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łączone | Bez wartości null, chyba że zadeklarowane z użyciem ? |
Deklaruje typ dopuszczający wartość null | Nie ma żadnego efektu |
Zmienne typu odwołania w kodzie skompilowanym w kontekście wyłączonym są nieświadome wartości null. Można przypisać literał lub zmienną może-null do zmiennej, która jest nieświadoma nullowalności. Jednak domyślny stan zmiennej o wartości null nie jest zerowy.
Możesz wybrać, które ustawienie jest najlepsze dla 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
: ustaw flagę ostrzeżenia na wyłączoną. -
#nullable enable warnings
: Ustaw flagę ostrzeżenia na włączoną. -
#nullable restore warnings
: przywraca flagę ostrzeżenia do ustawień projektu. -
#nullable disable annotations
: ustaw flagę adnotacji na wyłączoną. -
#nullable enable annotations
: ustaw flagę adnotacji na włączoną. -
#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żyj |
---|---|---|
domyślna wartość projektu | domyślna wartość projektu | Wartość domyślna |
Włącz | Wyłącz | Naprawianie ostrzeżeń dotyczących analizy |
Włącz | domyślna wartość projektu | Naprawianie ostrzeżeń dotyczących analizy |
domyślna wartość projektu | Włącz | Dodaj adnotacje typu |
Włącz | Włącz | Kod został już zmigrowany |
Wyłącz | Włącz | Dodawanie adnotacji do kodu przed naprawieniem ostrzeżeń |
Wyłącz | Wyłącz | Dodawanie starszego kodu do zmigrowanego projektu |
domyślna wartość projektu | Wyłącz | Rzadko |
Wyłącz | domyślna wartość projektu | Rzadko |
Te dziewięć kombinacji zapewnia szczegółową 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. Oznacza to, że interfejsy API w wygenerowanych plikach nie są adnotowane. Nie są generowane żadne ostrzeżenia o wartości null dla wygenerowanych plików. Istnieją cztery sposoby oznaczania pliku jako wygenerowanego:
- W pliku .editorconfig określ
generated_code = true
w sekcji, która ma zastosowanie do tego pliku. - 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. - Rozpocznij nazwę pliku od TemporaryGeneratedFile_
- Zakończ nazwę pliku tak, by kończyła się na .designer.cs, .generated.cs, .g.cs lub .g.i.cs.
Generatory mogą dobrowolnie wybrać korzystanie z dyrektywy preprocesora #nullable
.
Domyślnie flagi adnotacji dopuszczające wartość null i ostrzeżenia są wyłączone. Oznacza to, że istniejący kod kompiluje się bez zmian i bez generowania nowych ostrzeżeń. Począwszy od platformy .NET 6, nowe projekty obejmują element <Nullable>enable</Nullable>
we wszystkich szablonach projektów, ustawiając te flagi na włączone.
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.
Znane pułapki
Tablice i struktury zawierające typy referencyjne dopuszczające wartość null są znanymi pułapkami w analizie statycznej określającej bezpieczeństwo wartości null. W obu sytuacjach referencja nie dopuszczająca wartości null może zostać zainicjowana na wartość null
, bez generowania ostrzeżeń.
Struktury
Struktura, która zawiera typy referencyjne, które nie mogą mieć wartości null, umożliwia przypisywanie default
do niej bez żadnych ostrzeżeń. Rozważmy następujący przykład:
using System;
#nullable enable
public struct Student
{
public string FirstName;
public string? MiddleName;
public string LastName;
}
public static class Program
{
public static void PrintStudent(Student student)
{
Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");
Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
}
public static void Main() => PrintStudent(default);
}
W poprzednim przykładzie nie ma żadnego ostrzeżenia w PrintStudent(default)
, mimo że typy odwołań niemogące przyjmować wartości null, takie jak FirstName
i LastName
, mają wartość null.
Innym częstszym przypadkiem jest sytuacja, w której zajmujesz się strukturami ogólnymi. Rozważmy następujący przykład:
#nullable enable
public struct S<T>
{
public T Prop { get; set; }
}
public static class Program
{
public static void Main()
{
string s = default(S<string>).Prop;
}
}
W poprzednim przykładzie właściwość Prop
jest null
w czasie wykonywania. Jest przypisany do niemogącego przyjąć wartości null ciągu bez żadnych ostrzeżeń.
Tablice
Tablice są również znanymi pułapkami w typach odwołań przyjmujących wartości null. Rozważmy następujący przykład, który nie generuje żadnych ostrzeżeń:
using System;
#nullable enable
public static class Program
{
public static void Main()
{
string[] values = new string[10];
string s = values[0];
Console.WriteLine(s.ToUpper());
}
}
W poprzednim przykładzie deklaracja tablicy pokazuje, że zawiera niepuste ciągi, podczas gdy jej elementy są wszystkie zainicjowane na wartość null
. Następnie zmienna s
ma przypisaną null
wartość (pierwszy element tablicy). Na koniec zmienna s
jest wyłuszczana, powodując wyjątek środowiska uruchomieniowego.
Konstruktorzy
Konstruktor klasy będzie wciąż wywoływał finalizator, nawet gdy wystąpił wyjątek zgłoszony przez ten konstruktor.
Na poniższym przykładzie zobrazowano to zachowanie.
public class A
{
private string _name;
private B _b;
public A(string name)
{
ArgumentNullException.ThrowIfNullOrEmpty(name);
_name = name;
_b = new B();
}
~A()
{
Dispose();
}
public void Dispose()
{
_b.Dispose();
GC.SuppressFinalize(this);
}
}
public class B: IDisposable
{
public void Dispose() { }
}
public void Main()
{
var a = new A(string.Empty);
}
W poprzednim przykładzie System.NullReferenceException zostanie rzucony po uruchomieniu _b.Dispose();
, jeśli parametr name
był null
. Wywołanie _b.Dispose();
nigdy nie zostanie rzucone po pomyślnym zakończeniu konstruktora. Nie ma jednak ostrzeżenia wydanego przez kompilator, ponieważ analiza statyczna nie może określić, czy metoda (na przykład konstruktor) kończy się bez wystąpienia wyjątku w czasie wykonywania.