Wyrażanie intencji

Ukończone

W poprzedniej lekcji przedstawiono, w jaki sposób kompilator języka C# może wykonywać analizę statyczną, aby chronić funkcję .NullReferenceException Przedstawiono również sposób włączania kontekstu dopuszczanego do wartości null. W tej lekcji dowiesz się więcej na temat jawnego wyrażania intencji w kontekście dopuszczającym wartość null.

Deklarowanie zmiennych

Po włączeniu kontekstu dopuszczanego do wartości null masz lepszy wgląd w sposób, w jaki kompilator widzi kod. Możesz podjąć działania na ostrzeżenia wygenerowane na podstawie kontekstu z obsługą wartości null i w ten sposób jawnie definiujesz intencje. Na przykład kontynuujmy badanie kodu i przejmijmy FooBar deklarację i przypisanie:

// Define as nullable
FooBar? fooBar = null;

Zanotuj dodany element ? do FooBarelementu . Informuje to kompilator, że jawnie zamierzasz fooBar mieć wartość null. Jeśli nie zamierzasz fooBar mieć wartości null, ale nadal chcesz uniknąć ostrzeżenia, rozważ następujące kwestie:

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

W tym przykładzie dodano operator null-forgiving (!) do nullelementu , który instruuje kompilator, że jawnie inicjujesz tę zmienną jako null. Kompilator nie będzie wystawiał ostrzeżeń o tym odwołaniu o wartości null.

Dobrym rozwiązaniem jest przypisanie zmiennych innych niż null,null gdy są zadeklarowane, jeśli jest to możliwe:

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

Operatory

Zgodnie z opisem w poprzedniej lekcji język C# definiuje kilka operatorów do wyrażania intencji wokół typów odwołań dopuszczanych do wartości null.

Operator null-forgiving (!)

W poprzedniej sekcji przedstawiono operator forgiving o wartości null (!). Informuje kompilator o zignorowaniu ostrzeżenia CS8600. Jest to jeden ze sposobów na powiedzenie kompilatorowi, że wiesz, co robisz, ale jest to zastrzeżenie, że w rzeczywistości powinieneś wiedzieć, co robisz!

Podczas inicjowania typów niezwiązanych z wartościami null podczas włączania kontekstu dopuszczalnego wartości null może być konieczne jawne zapytanie kompilatora o przebaczenie. Rozważmy na przykład następujący kod:

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

W poprzednim przykładzie FooBar fooBar = fooList.Find(f => f.Name == "Bar"); generuje ostrzeżenie CS8600, ponieważ Find może zwrócić wartość null. Możliwe null byłoby przypisanie do fooBarelementu , który jest niepusty w tym kontekście. Jednak w tym spisanym przykładzie wiemy, że Find nigdy nie zwróci null się zgodnie z zapisem. Tę intencję można wyrazić dla kompilatora za pomocą operatora forgiving o wartości null:

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

Zanotuj wartość ! na końcu .fooList.Find(f => f.Name == "Bar") Informuje to kompilator, że wiadomo, że obiekt zwrócony przez Find metodę może mieć nullwartość i jest w porządku.

Operator forgiving o wartości null można zastosować do wbudowanego obiektu przed wywołaniem metody lub oceną właściwości. Rozważmy inny spisany przykład:

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

W powyższym przykładzie:

  • GetFooList jest metodą statyczną, która zwraca typ dopuszczający wartość null, List<FooBar>?.
  • fooList element ma przypisaną wartość zwracaną przez GetFooList.
  • Kompilator generuje ostrzeżenie, fooList.Find(f => f.Name == "Bar"); ponieważ wartość przypisana do fooList elementu może mieć wartość null.
  • Przy założeniufooList, że parametr nie nulljest wartością , możenullale wiemy, że tak nie będzie, więc jest stosowany operator forgiving o wartości null.

Aby wyłączyć ostrzeżenie, możesz zastosować operator fooList forgiving o wartości null:

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

Uwaga

Należy używać operatora forgiving o wartości null w rozsądny sposób. Użycie go po prostu do odrzucenia ostrzeżenia oznacza, że mówisz kompilatorowi, aby nie ułatwić odnajdywania możliwych wpadek o wartości null. Używaj go oszczędnie i tylko wtedy, gdy masz pewność.

Aby uzyskać więcej informacji, zobacz ! Operator (null-forgiving) (odwołanie w C#).

Operator łączenia wartości null (??)

Podczas pracy z typami dopuszczanymi wartościami null może być konieczne sprawdzenie, czy są one obecnie null i podejmą określone działania. Jeśli na przykład przypisano null typ dopuszczalny do wartości null lub jest niezainicjowany, może być konieczne przypisanie im wartości innej niż null. W tym miejscu przydaje się operator łączenia wartości null (??).

Rozważmy następujący przykład:

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

    // Safely use salesTax object.
}

W poprzednim kodzie języka C#:

  • Parametr salesTax jest definiowany jako dopuszczany IStateSalesTaxdo wartości null.
  • W treści salesTax metody parametr jest warunkowo przypisywany przy użyciu operatora łączenia wartości null.
    • Gwarantuje to, że jeśli salesTax została przekazana w taki sposób null , że będzie miała wartość.

Napiwek

Jest to funkcjonalnie równoważne z następującym kodem języka C#:

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

    // Safely use salesTax object.
}

Oto przykład innego wspólnego idiomu języka C#, w którym może być przydatny operator łączenia wartości null:

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();
}

Poprzedni kod języka C#:

  • Definiuje ogólną klasę otoki, w której parametr typu ogólnego jest ograniczony do new()klasy .
  • Konstruktor akceptuje T source parametr, który jest domyślnie ustawiony na null.
  • Opakowana _source jest warunkowo inicjowana do klasy new T().

Aby uzyskać więcej informacji, zobacz ?? i ?? = operatory (odwołanie w C#).

Operator warunkowy o wartości null (?.)

Podczas pracy z typami dopuszczanymi wartościami null może być konieczne warunkowe wykonywanie akcji na podstawie stanu null obiektu. Na przykład: w poprzedniej lekcji FooBar rekord został użyty do zademonstrowania NullReferenceException przez wyłudanie nullelementu . Zostało to spowodowane wywołaniami.ToString Rozważmy ten sam przykład, ale teraz zastosuj operator warunkowy o wartości 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);

Poprzedni kod języka C#:

  • Warunkowe wyłudzenie fooBar, przypisanie wyniku ToString do zmiennej str .
    • Zmienna str jest typu string? (ciąg dopuszczalny do wartości null).
  • Zapisuje wartość str do standardowych danych wyjściowych, co nie jest niczym.
  • Wywołanie Console.Write(null) jest prawidłowe, więc nie ma żadnych ostrzeżeń.
  • Jeśli chcesz wywołać Console.Write(str.Length) metodę , zostanie wyświetlone ostrzeżenie, ponieważ potencjalnie wyłuszczenie wartości null.

Napiwek

Jest to funkcjonalnie równoważne z następującym kodem języka 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);

Możesz połączyć operator, aby dodatkowo wyrazić swoją intencję. Można na przykład utworzyć łańcuch ?. operatorów i ?? :

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

Aby uzyskać więcej informacji, zapoznaj się z operatorami ?. i ?[] (warunkowe wartości null).

Podsumowanie

W tej lekcji przedstawiono sposób wyrażania intencji dopuszczalności null w kodzie. W następnej lekcji zastosujesz zdobytą wiedzę do istniejącego projektu.