Uttrycka avsikt

Slutförd

I föregående lektion lärde du dig hur C#-kompilatorn kan utföra statisk analys för att skydda mot NullReferenceException. Du har också lärt dig hur du aktiverar en nullbar kontext. I den här lektionen lär du dig mer om att uttryckligen uttrycka din avsikt i en nullbar kontext.

Deklarera variabler

Med en nullbar kontext aktiverad har du mer insyn i hur kompilatorn ser din kod. Du kan agera på varningarna som genereras från en nullbar-aktiverad kontext, och när du gör det definierar du uttryckligen dina avsikter. Vi fortsätter till exempel att FooBar undersöka koden och granska deklarationen och tilldelningen:

// Define as nullable
FooBar? fooBar = null;

Observera den ? tillagda i FooBar. Detta talar om för kompilatorn att du uttryckligen har för avsikt fooBar att vara nullbar. Om du inte tänker fooBar vara null, men ändå vill undvika varningen, bör du tänka på följande:

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

Det här exemplet lägger till operatorn null-forgiving (!) till null, som instruerar kompilatorn att du uttryckligen initierar den här variabeln som null. Kompilatorn utfärdar inte varningar om att den här referensen är null.

En bra idé är att tilldela icke-nullbara variabler icke-värdennull när de deklareras, om möjligt:

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

Operatorer

Enligt beskrivningen i föregående lektion definierar C# flera operatorer för att uttrycka din avsikt kring nullbara referenstyper.

Null-förlåtande operator (!)

Du introducerades för operatorn null-forgiving (!) i föregående avsnitt. Den uppmanar kompilatorn att ignorera CS8600-varningen. Detta är ett sätt att berätta för kompilatorn att du vet vad du gör, men det kommer med förbehållet att du faktiskt borde veta vad du gör!

När du initierar icke-nullbara typer medan en nullbar kontext är aktiverad kan du behöva uttryckligen be kompilatorn om förlåtelse. Tänk till exempel på följande 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);

I föregående exempel FooBar fooBar = fooList.Find(f => f.Name == "Bar"); genererar en CS8600-varning, eftersom Find kan returnera null. Detta skulle null kunna tilldelas till fooBar, vilket inte kan null-värdet i den här kontexten. Men i det här invecklade exemplet vet vi att det aldrig kommer att Find återvända null som skrivet. Du kan uttrycka den här avsikten för kompilatorn med operatorn null-forgiving:

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

! Observera i slutet av fooList.Find(f => f.Name == "Bar"). Detta meddelar kompilatorn att du vet att objektet som returneras av Find metoden kan vara null, och att det är okej.

Du kan använda operatorn null-forgiving på ett objekt infogat före ett metodanrop eller egenskapsutvärdering. Överväg ett annat intrikat exempel:

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

I exemplet ovan händer följande:

  • GetFooList är en statisk metod som returnerar en nullbar typ, List<FooBar>?.
  • fooList tilldelas värdet som returneras av GetFooList.
  • Kompilatorn genererar en varning på fooList.Find(f => f.Name == "Bar"); eftersom det tilldelade värdet fooList kan vara null.
  • Förutsatt att fooList inte nullär , Find kan returnera null, men vi vet att det inte gör det, så operatorn null-forgiving tillämpas.

Du kan använda operatorn null-forgiving för att fooList inaktivera varningen:

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

Kommentar

Du bör använda operatorn null-forgiving omdömesgillt. Om du bara använder den för att stänga en varning innebär det att du säger till kompilatorn att inte hjälpa dig att upptäcka möjliga null-missöden. Använd den sparsamt och endast när du är säker.

Mer information finns i ! (null-förlåtande) operator (C#-referens).

Operatorn Null-coalescing (??)

När du arbetar med null-typer kan du behöva utvärdera om de är aktuella null och vidta vissa åtgärder. Om en nullbar typ till exempel antingen har tilldelats null eller om de är onitialiserade kan du behöva tilldela dem ett värde som inte är null. Det är där operatorn null-coalescing (??) är användbar.

Ta följande som exempel:

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

    // Safely use salesTax object.
}

I föregående C#-kod:

  • Parametern salesTax definieras som en nullbar IStateSalesTax.
  • I metodtexten salesTax tilldelas villkorsstyrt med hjälp av operatorn null-coalescing.
    • Detta säkerställer att om salesTax skickades in som null att det kommer att ha ett värde.

Dricks

Detta motsvarar funktionellt följande C#-kod:

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

    // Safely use salesTax object.
}

Här är ett exempel på ett annat vanligt C#-formspråk där operatorn null-coalescing kan vara användbar:

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

Föregående C#-kod:

  • Definierar en allmän omslutningsklass, där den generiska typparametern är begränsad till new().
  • Konstruktorn accepterar en T source parameter som är standardvärdet .null
  • Den omslutna initieras _source villkorligt till en new T().

Mer information finns i ?? och ?? = operatorer (C#-referens).

Null-villkorsstyrd operator (?.)

När du arbetar med null-typer kan du behöva utföra åtgärder villkorligt baserat på tillståndet för ett null objekt. Till exempel: i föregående enhet FooBar användes posten för att demonstrera NullReferenceException genom att dereferencing null. Detta orsakades när det ToString anropades. Tänk på samma exempel, men tillämpa nu den null-villkorsstyrda operatorn:

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

Föregående C#-kod:

  • Villkorsstyrd avreferering fooBar, tilldela resultatet av ToString till variabeln str .
    • Variabeln str är av typen string? (nullbar sträng).
  • Det skriver värdet str för till standardutdata, vilket inte är någonting.
  • Anropet Console.Write(null) är giltigt, så det finns inga varningar.
  • Du skulle få en varning om du skulle anropa Console.Write(str.Length) eftersom du potentiellt skulle avreferera null.

Dricks

Detta motsvarar funktionellt följande C#-kod:

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

Du kan kombinera operatorn för att ytterligare uttrycka din avsikt. Du kan till exempel länka operatorerna ?. och ?? :

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

Mer information finns i operatorerna ?. och ?[] (null-conditional).

Sammanfattning

I den här lektionen har du lärt dig hur du uttrycker avsikten med nullabilitet i kod. I nästa lektion ska du tillämpa det du har lärt dig för ett befintligt projekt.

Kontrollera dina kunskaper

1.

Vad är värdet för default string referenstypen?

2.

Vad är det förväntade beteendet med avreferencing null?

3.

Vad händer när den här throw null; C#-koden körs?

4.

Vilken instruktion är mest exakt när det gäller nullbara referenstyper?