Principy nullability

Dokončeno

Pokud jste vývojář .NET, je pravděpodobné, že jste narazili na System.NullReferenceException. K tomu dochází v době běhu, když null je dereferenced; to znamená, že když je proměnná vyhodnocena za běhu, ale proměnná odkazuje na null. Tato výjimka je zdaleka nejčastější výjimkou v ekosystému .NET. Tvůrce null, Sir Tony Hoare, se označuje null jako "miliarda dolarů chyba".

V následujícím příkladu FooBar je proměnná přiřazena null a okamžitě se dereferenced, čímž dochází k problému:

// 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);

Problém se stává mnohem obtížnější odhalit jako vývojář, když se vaše aplikace zvětšují a zkomplikují. Zobrazení potenciálních chyb, jako je tato, je úlohou pro nástroje a kompilátor jazyka C# vám pomůže.

Definování zabezpečení null

Pojem zabezpečení null definuje sadu funkcí specifických pro typy s možnou hodnotou null, které pomáhají snížit počet možných NullReferenceException výskytů.

Vzhledem k předchozímu FooBar příkladu NullReferenceException byste se mohli vyhnout kontrole, jestli fooBar byla null proměnná před jeho zrušením:

// 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);

Kompilátor může při identifikaci podobných scénářů odvodit záměr vašeho kódu a vynutit požadované chování. To je však pouze v případě, že je povolen kontext s možnou hodnotou null. Než probereme kontext s možnou hodnotou null, popíšeme možné typy s možnou hodnotou null.

Typy s povolenou hodnotou Null

Před jazykem C# 2.0 byly pouze odkazové typy s možnou hodnotou null. Typy hodnot, například int nebo DateTime nešlo.null Pokud jsou tyto typy inicializovány bez hodnoty, vrátí se k jejich default hodnotě. V případě int, to je 0. Pro , DateTimeje DateTime.MinValueto .

Odkazové typy, které se vytvoří instance bez počátečních hodnot, fungují odlišně. Hodnota default pro všechny odkazové typy je null.

Vezměte v úvahu následující fragment kódu jazyka 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

V předchozím příkladu:

  • first je null to proto, že byl deklarován typ string odkazu, ale nebyl proveden žádný přiřazení.
  • second je přiřazena string.Empty , když je deklarována. Objekt nikdy neměl null přiřazení.
  • third0 navzdory nepřiřazování. Je to struct (hodnota-typ) a má default hodnotu 0.
  • date je neinicializován, ale jeho default hodnota je System.DateTime.MinValue.

Počínaje jazykem C# 2.0 můžete definovat typy hodnot s možnou hodnotou null pomocí Nullable<T> (nebo T? pro zkratku). To umožňuje, aby typy hodnot měly hodnotu null. Vezměte v úvahu následující fragment kódu jazyka 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

V předchozím příkladu:

  • first je null to proto, že typ hodnoty s možnou hodnotou null není inicializován.
  • second je přiřazena null , když je deklarována.
  • third je null hodnota default pro Nullable<int> hodnotu null.
  • fourth je 0 jako new() výraz volá Nullable<int> konstruktor a int je 0 ve výchozím nastavení.

Jazyk C# 8.0 zavedl odkazové typy s možnou hodnotou null, kde můžete vyjádřit svůj záměr, že odkazový typ může být null nebo je vždy ne-null. Možná si myslíte, že "Myslel jsem, že všechny odkazové typy jsou nullable!" Ty se mýlíš a oni jsou. Tato funkce umožňuje vyjádřit svůj záměr, který se kompilátor pak pokusí vynutit. Stejná T? syntaxe vyjadřuje, že typ odkazu má být nullable.

Vezměte v úvahu následující fragment kódu jazyka C#:

#nullable enable

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

V předchozím příkladu kompilátor odvodí váš záměr následujícím způsobem:

  • first není nikdy null tak, jak je to rozhodně přiřazeno.
  • secondby nikdy neměl být null, i když to je původně null. second Vyhodnocením před přiřazením hodnoty dojde k upozornění kompilátoru, protože není inicializováno.
  • thirdmůže být null. Může například odkazovat na , System.Stringale může odkazovat na null. Některé z těchto variant jsou přijatelné. Kompilátor vám pomůže tím, že vás upozorní, pokud se dereference third bez první kontroly, že není null.

Důležité

Aby bylo možné použít funkci odkazových typů s možnou hodnotou null, jak je znázorněno výše, musí být v kontextu s možnou hodnotou null. Toto je podrobně popsáno v další části.

Kontext s možnou hodnotou null

Kontexty s možnou hodnotou null umožňují jemně odstupňovaný ovládací prvek, jak kompilátor interpretuje proměnné referenčního typu. Existují čtyři možné kontexty s možnou hodnotou null:

  • disable: Kompilátor se chová podobně jako C# 7.3 a starší.
  • enable: Kompilátor povolí všechny referenční analýzy null a všechny jazykové funkce.
  • warnings: Kompilátor provádí všechny analýzy null a generuje upozornění, když kód může dereference null.
  • annotations: Kompilátor neprovádí analýzu null nebo generuje upozornění, když kód může dereference null, ale můžete stále opatřit poznámkami kódu pomocí referenčních typů ? s možnou hodnotou null a operátorů odpustit od verze null (!).

Tento modul je vymezený na disable enable kontexty s možnou hodnotou null. Další informace najdete v referenčních typech odkazů s možnou hodnotou Null: kontexty s možnou hodnotou Null.

Povolit odkazové typy s možnou hodnotou null

V souboru projektu C# (.csproj) přidejte podřízený <Nullable> uzel do elementu <Project> (nebo připojte k existujícímu <PropertyGroup>). Tím se použije kontext s možnou enable hodnotou null pro celý projekt.

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

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

    <!-- Omitted for brevity -->

</Project>

Případně můžete nastavit rozsah kontextu s možnou hodnotou null na soubor jazyka C# pomocí direktivy kompilátoru.

#nullable enable

Předchozí direktiva kompilátoru jazyka C# je funkčně ekvivalentní konfiguraci projektu, ale je vymezena na soubor, ve kterém se nachází. Další informace najdete v tématu Odkazové typy s možnou hodnotou Null: Kontexty s možnou hodnotou Null (docs)

Důležité

Kontext s možnou hodnotou null je ve výchozím nastavení povolen v souboru .csproj ve všech šablonách projektů jazyka C# počínaje rozhraním .NET 6.0 a vyšším.

Pokud je povolený kontext s možnou hodnotou null, zobrazí se nová upozornění. Představte si předchozí FooBar příklad, který obsahuje dvě upozornění při analýze v kontextu s možnou hodnotou null:

  1. Řádek FooBar fooBar = null; obsahuje upozornění na null přiřazení: Upozornění C# CS8600: Převod literálu null nebo možné hodnoty null na nenulový typ.

    Snímek obrazovky s upozorněním C# CS8600: Převod literálu null nebo možné hodnoty null na nenulový typ

  2. Řádek _ = fooBar.ToString(); má také upozornění. Tentokrát se kompilátor obává, že fooBar může mít hodnotu null: Upozornění C# CS8602: Dereference pravděpodobně nulového odkazu.

    Snímek obrazovky s upozorněním C# CS8602: Dereference možného odkazu null

Důležité

Neexistuje žádná zaručená bezpečnost null, i když reagujete na a eliminujete všechna upozornění. Existují některé omezené scénáře, které budou předávat analýzu kompilátoru, ale výsledkem modulu runtime NullReferenceException.

Shrnutí

V této lekci jste se naučili povolit kontext s možnou hodnotou null v jazyce C#, který pomáhá chránit NullReferenceExceptionproti . V další lekci se dozvíte více o explicitní vyjádření záměru v kontextu s možnou hodnotou null.