Vyjádření záměru

Dokončeno

V předchozí lekci jste se dozvěděli, jak kompilátor jazyka C# může provádět statickou analýzu, která pomáhá chránit NullReferenceExceptionproti . Dozvěděli jste se také, jak povolit kontext s možnou hodnotou null. V této lekci se dozvíte více o explicitní vyjádření záměru v kontextu s možnou hodnotou null.

Deklarace proměnných

S povoleným kontextem s možnou hodnotou null máte lepší přehled o tom, jak kompilátor vidí váš kód. Můžete reagovat na upozornění vygenerovaná z kontextu s povolenou hodnotou null a přitom explicitně definujete své záměry. Pojďme například pokračovat v prozkoumání FooBar kódu a prozkoumání deklarace a přiřazení:

// Define as nullable
FooBar? fooBar = null;

Všimněte si přidaného ? do FooBar. To kompilátoru říká, že explicitně máte v úmyslu fooBar mít hodnotu null. Pokud nemáte v úmyslu fooBar mít hodnotu null, ale přesto se chcete upozornění vyhnout, zvažte následující:

// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;

Tento příklad přidá operátor null-forgiving (!), který nulldává kompilátoru pokyn, že explicitně inicializujete tuto proměnnou jako null. Kompilátor nebude vydávat upozornění na tento odkaz s hodnotou null.

Osvědčeným postupem je přiřadit proměnnénull bez hodnoty null, pokud jsou deklarovány, pokud je to možné:

// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");

Operátory

Jak je popsáno v předchozí lekci, jazyk C# definuje několik operátorů, které vyjadřují váš záměr ohledně typů odkazů s možnou hodnotou null.

Operátor null-forgiving (!)

V předchozí části jste se seznámili s operátorem null-forgiving (!). Říká kompilátoru, aby ignoroval upozornění CS8600. To je jeden ze způsobů, jak kompilátoru říct, že víte, co děláte, ale přináší upozornění, že byste měli vědět, co děláte!

Při inicializaci nenulových typů v době, kdy je povolen kontext s možnou hodnotou null, může být nutné explicitně požádat kompilátor o odpuštění. Představte si například následující kód:

#nullable enable

using System.Collections.Generic;

var fooList = new List<FooBar>
{
    new(Id: 1, Name: "Foo"),
    new(Id: 2, Name: "Bar")
};

FooBar fooBar = fooList.Find(f => f.Name == "Bar");

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

V předchozím příkladu FooBar fooBar = fooList.Find(f => f.Name == "Bar"); vygeneruje upozornění CS8600, protože Find může vrátit null. To by bylo možné null přiřadit fooBar, což je v tomto kontextu nenulové. V tomto nepomýšleném příkladu však víme, že Find se nikdy nevrátí null jako napsaný. Tento záměr můžete vyjádřit kompilátoru pomocí operátoru null-forgiving:

FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;

Poznamenejte si na ! konci .fooList.Find(f => f.Name == "Bar") To kompilátoru říká, že víte, že objekt vrácený metodou Find může být nulla je v pořádku.

Operátor null-forgiving můžete použít na objekt vložený před voláním metody nebo vyhodnocením vlastnosti. Představte si jiný příklad:

List<FooBar>? fooList = FooListFactory.GetFooList();

// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning

static class FooListFactory
{
    public static List<FooBar>? GetFooList() =>
        new List<FooBar>
        {
            new(Id: 1, Name: "Foo"),
            new(Id: 2, Name: "Bar")
        };
}

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

V předchozím příkladu:

  • GetFooList je statická metoda, která vrací typ s možnou hodnotou null, List<FooBar>?.
  • fooList je přiřazena hodnota vrácená GetFooListhodnotou .
  • Kompilátor vygeneruje upozornění, fooList.Find(f => f.Name == "Bar"); protože hodnota přiřazená fooList může být null.
  • Za předpokladufooList, nullže není , Find může se vrátit null, ale víme, že to nebude, takže se použije operátor null-odpustit.

Pokud chcete upozornění zakázat, můžete použít operátor fooList null-forgiving:

FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;

Poznámka:

Operátor null-forgiving byste měli použít uvážlivě. Když ji jednoduše použijete k zavření upozornění, znamená to, že kompilátoru říkáte, aby vám nepomohl zjistit možnou chybnou hodnotu null. Používejte ji střídmě a jen tehdy, když jste si jistí.

Další informace najdete v tématu ! (null-forgiving) – operátor (referenční dokumentace jazyka C#).

Operátor null-coalescing (??)

Při práci s typy s možnou hodnotou null možná budete muset vyhodnotit, jestli jsou aktuálně null dostupné, a provést určitou akci. Pokud je například přiřazený null typ s možnou hodnotou null nebo je neinicializován, budete je možná muset přiřadit nenulovou hodnotu. To je místo, kde je užitečný operátor nulového sjednocení (??).

Představte si následující příklad:

public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
    salesTax ??= DefaultStateSalesTax.Value;

    // Safely use salesTax object.
}

V předchozím kódu jazyka C#:

  • Parametr salesTax je definován jako nullable IStateSalesTax.
  • V těle salesTax metody je podmíněně přiřazen pomocí operátoru null-coalescing.
    • Tím se zajistí, že pokud salesTax byla předána, protože null bude mít hodnotu.

Tip

Toto je funkčně ekvivalentní následujícímu kódu jazyka C#:

public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
    if (salesTax is null)
    {
        salesTax = DefaultStateSalesTax.Value;
    }

    // Safely use salesTax object.
}

Tady je příklad jiného běžného idiomu jazyka C#, kde může být užitečný operátor nulového sjednocení:

public sealed class Wrapper<T> where T : new()
{
    private T _source;

    // If given a source, wrap it. Otherwise, wrap a new source:
    public Wrapper(T source = null) => _source = source ?? new T();
}

Předchozí kód jazyka C#:

  • Definuje obecnou třídu obálky, kde je parametr obecného typu omezen na new().
  • Konstruktor přijímá T source parametr, který je ve výchozím nastavení null.
  • Obálka _source je podmíněně inicializována new T()na .

Další informace najdete v tématu ?? a ?? = operátory (referenční dokumentace jazyka C#).

Operátor s podmínkou null (?.)

Při práci s typy s možnou hodnotou null může být nutné podmíněně provádět akce na základě stavu objektu null . Příklad: v předchozí lekci FooBar se záznam použil k předvedení NullReferenceException dereferencingem null. To bylo způsobeno tím, že byl ToString volána. Podívejte se na stejný příklad, ale teď použijte operátor s podmínkou null:

using System;

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

// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);

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

Předchozí kód jazyka C#:

  • Podmíněně dereference fooBar, přiřaďte výsledek ToString proměnné str .
    • Proměnná str je typu string? (řetězec s možnou hodnotou null).
  • Zapíše hodnotu do standardního str výstupu, což není nic.
  • Volání Console.Write(null) je platné, takže neexistují žádná upozornění.
  • Pokud byste chtěli volat Console.Write(str.Length) , zobrazilo by se upozornění, protože by se mohlo stát, že byste mohli odvodit hodnotu null.

Tip

Toto je funkčně ekvivalentní následujícímu kódu jazyka C#:

using System;

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

// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);

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

Operátor můžete zkombinovat a vyjádřit tak váš záměr. Můžete například zřetězit operátory a ?? operátory?.:

FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown

Další informace najdete v operátorech ?. a ?[] (s podmínkou null).

Shrnutí

V této lekci jste se dozvěděli o vyjádření záměru nullability v kódu. V další lekci použijete to, co jste se naučili u existujícího projektu.

Kontrola znalostí

1.

Jaká je default hodnota string typu odkazu?

2.

Jaké je očekávané chování dereferencování null?

3.

Co se stane, když se tento throw null; kód jazyka C# spustí?

4.

Který příkaz je nejpřesnější ohledně typů odkazů s možnou hodnotou null?