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że zwrócić wartość null, Find ale 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.

Sprawdź swoją wiedzę

1.

Jaka jest default wartość string typu odwołania?

2.

Jakie jest oczekiwane zachowanie wyłudzania null?

3.

Co się stanie po wykonaniu tego throw null; kodu w języku C#?

4.

Która instrukcja jest najbardziej dokładna w odniesieniu do typów odwołań dopuszczających wartość null?