Vyjádření záměru
- 5 min
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 proti NullReferenceException. 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 (!) k null, což dává pokyn kompilátoru, ž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, pokud je to možné, při deklaraci přiřadit vašim proměnným, kde nejsou povolené hodnoty null, hodnoty ne-null.
// 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 vymyšleném příkladu však víme, že Find se nikdy nevrátí null jak je napsáno. Tento záměr můžete vyjádřit kompilátoru pomocí operátoru null-forgiving:
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;
Všimněte 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 přímo na objekt 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:
-
GetFooListje statická metoda, která vrací typ s možnou hodnotou null,List<FooBar>?. -
fooListje přiřazena hodnota vrácenáGetFooList. - Kompilátor vygeneruje upozornění kvůli
fooList.Find(f => f.Name == "Bar");, protože hodnota přiřazenáfooListmůže býtnull. - Za předpokladu, že
fooListnenínull,Findmůže vrátitnull, ale víme, že to neudělá, takže je použit operátor odpouštění null.
Operátor fooList null-forgiving můžete použít k zakázání upozornění:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
Poznámka:
Měli byste operátor null-forgiving 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 aktuálně mají hodnotu null, a přijmout odpovídající opatření. Například když je typu s možnou hodnotou null přiřazena null nebo je neinitializován, budete mu možná muset přiřadit nenulovou hodnotu. Tam 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
salesTaxje definován jako nullableIStateSalesTax. - V těle metody je výraz
salesTaxpodmíněně přiřazen pomocí operátoru slučování s nulou.- Tím se zajistí, že pokud by
salesTaxbylo předáno jakonull, bude mít hodnotu.
- Tím se zajistí, že pokud by
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á parametr
T source, který má výchozí hodnotunull. - Zabalený
_sourceje podmíněně inicializován nanew T().
Další informace najdete, pokud se podíváte na operátory ?? a ??= (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 dereferencováním null. To bylo způsobeno tím, že byl ToString volán. 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ě dereferencuje
fooBara přiřazuje výsledek zToStringproměnnéstr.- Proměnná
strje typustring?(řetězec s možnou hodnotou null).
- Proměnná
- Zapíše hodnotu
strdo standardního výstupu, což je nic. - Volání
Console.Write(null)je platné, takže neexistují žádná upozornění. - Pokud byste zavolali
Console.Write(str.Length), zobrazilo by se upozornění, protože byste potenciálně dereferencovali 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, abyste tak lépe vyjádřili váš záměr. Například můžete zřetězit operátory ?. a ??.
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.