Förstå nullbarhet

Slutförd

Om du är .NET-utvecklare är chansen stor att du har stött på System.NullReferenceException. Detta inträffar vid körning när en null är dereferenced, det vill: när en variabel utvärderas vid körning, men variabeln refererar till null. Det här undantaget är det överlägset vanligaste undantaget i .NET-ekosystemet. Skaparen av null, Sir Tony Hoare, refererar till null som "miljard-dollar misstaget.".

I följande exempel tilldelas variabeln FooBar till null och avrefereras omedelbart, vilket visar problemet:

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

// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();

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

Problemet blir mycket svårare att upptäcka som utvecklare när dina appar växer i storlek och komplexitet. Att upptäcka potentiella fel som detta är ett jobb för verktyg, och C#-kompilatorn är här för att hjälpa till.

Definiera nullsäkerhet

Termen nullsäkerhet definierar en uppsättning funktioner som är specifika för null-typer som hjälper till att minska antalet möjliga NullReferenceException förekomster.

Med tanke på föregående FooBar exempel kan du undvika NullReferenceException genom att kontrollera om variabeln fooBar var null innan du avrefererar den:

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

// Check for null
if (fooBar is not null)
{
    _ = fooBar.ToString();
}

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

För att hjälpa till med att identifiera scenarier som detta kan kompilatorn härleda avsikten med din kod och framtvinga önskat beteende. Detta är dock bara när en nullbar kontext är aktiverad. Innan vi diskuterar nullbar kontext ska vi beskriva de möjliga typerna av null-värden.

Typer som kan ogiltigas

Före C# 2.0 var endast referenstyper null. Värdetyper som int eller DateTime kunde inte vara null. Om dessa typer initieras utan ett värde återgår de till sitt default värde. När det gäller är intdetta 0. För en DateTimeär DateTime.MinValuedet .

Referenstyper som instansieras utan inledande värden fungerar annorlunda. Värdet default för alla referenstyper är null.

Överväg följande C#-kodfragment:

string first;                  // first is null
string second = string.Empty   // second is not null, instead it's an empty string ""
int third;                     // third is 0 because int is a value type
DateTime date;                 // date is DateTime.MinValue

I exemplet ovan händer följande:

  • first beror null på att referenstypen string deklarerades men ingen tilldelning gjordes.
  • second tilldelas string.Empty när den deklareras. Objektet har aldrig haft någon null tilldelning.
  • third är 0 trots att den inte har tilldelats. Det är en struct (värdetyp) och har värdet default 0.
  • date är onitialiserad, men dess default värde är System.DateTime.MinValue.

Från och med C# 2.0 kan du definiera nullable value types using Nullable<T> (eller T? för shorthand). Detta gör att värdetyper kan vara nullbara. Överväg följande C#-kodfragment:

int? first;            // first is implicitly null (uninitialized)
int? second = null;    // second is explicitly null
int? third = default;  // third is null as the default value for Nullable<Int32> is null
int? fourth = new();    // fourth is 0, since new calls the nullable constructor

I exemplet ovan händer följande:

  • first beror null på att den nullbara värdetypen är onitialiserad.
  • second tilldelas null när den deklareras.
  • third är null som värdet default för Nullable<int> är null.
  • fourth är 0 som new() uttrycket anropar Nullable<int> konstruktorn och int är 0 som standard.

C# 8.0 introducerade nullbara referenstyper, där du kan uttrycka din avsikt att en referenstyp kan vara null eller alltid är icke-null. Du kanske tänker: "Jag trodde att alla referenstyper är nullbara!" Du har inte fel, och det är de. Med den här funktionen kan du uttrycka din avsikt, som kompilatorn sedan försöker tillämpa. Samma T? syntax uttrycker att en referenstyp är avsedd att vara null.

Överväg följande C#-kodfragment:

#nullable enable

string first = string.Empty;
string second;
string? third;

Med hjälp av föregående exempel härleder kompilatorn din avsikt på följande sätt:

  • first är aldrig null som det definitivt tilldelas.
  • secondbör aldrig vara null, även om det är från början null. second Utvärdering innan du tilldelar ett värde resulterar i en kompilatorvarning eftersom den är onitialiserad.
  • thirdkan vara null. Det kan till exempel peka på en System.String, men den kan peka på null. Någon av dessa varianter är godtagbara. Kompilatorn hjälper dig genom att varna dig om du avrefereras third utan att först kontrollera att den inte är null.

Viktigt!

För att kunna använda funktionen för null-referenstyper som visas ovan måste den ligga inom en nullbar kontext. Detta beskrivs i nästa avsnitt.

Nullbar kontext

Nullbara kontexter möjliggör detaljerad kontroll för hur kompilatorn tolkar referenstypvariabler. Det finns fyra möjliga null-kontexter:

  • disable: Kompilatorn fungerar på samma sätt som C# 7.3 och tidigare.
  • enable: Kompilatorn aktiverar alla null-referensanalyser och alla språkfunktioner.
  • warnings: Kompilatorn utför alla null-analyser och avger varningar när koden kan avreferera null.
  • annotations: Kompilatorn utför inte null-analys eller avger varningar när kod kan avreferera null, men du kan fortfarande kommentera koden med hjälp av null-referenstyper ? och null-förlåtande operatorer (!).

Den här modulen är begränsad till antingen disable eller enable nullbara kontexter. Mer information finns i Referenstyper som kan ogiltigförklaras: Nullbara kontexter.

Aktivera null-referenstyper

I C#-projektfilen (.csproj) lägger du till en underordnad <Nullable> nod i elementet <Project> (eller lägger till i en befintlig <PropertyGroup>). Detta tillämpar den enable nullbara kontexten på hela projektet.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <!-- Omitted for brevity -->

</Project>

Du kan också omfångsbegränsa nullbar kontext till en C#-fil med hjälp av ett kompileringsdirektiv.

#nullable enable

Det föregående C#-kompileringsdirektivet är funktionellt likvärdigt med projektkonfigurationen, men det är begränsat till filen där det finns. Mer information finns i Nullable reference types: Nullable contexts (docs)

Viktigt!

Den nullbara kontexten är aktiverad i .csproj-filen som standard i alla C#-projektmallar som börjar med .NET 6.0 och senare.

När den nullbara kontexten är aktiverad får du nya varningar. Tänk på föregående FooBar exempel, som har två varningar när de analyseras i en nullbar kontext:

  1. Raden FooBar fooBar = null; har en varning om tilldelningen null : C# Varning CS8600: Konvertera nullliteral eller möjligt null-värde till icke-nullbar typ.

    Skärmbild av C# Varning CS8600: Konvertera nullliteral eller möjligt null-värde till icke-nullbar typ.

  2. Linjen _ = fooBar.ToString(); har också en varning. Den här gången är kompilatorn orolig för att fooBar den kan vara null: C# Warning CS8602: Dereference of a possibly null reference(C# Warning CS8602: Dereference of a possibly null reference).

    Skärmbild av C# Varning CS8602: Referens för en möjligen null-referens.

Viktigt!

Det finns ingen garanterad null-säkerhet, även om du reagerar på och eliminerar alla varningar. Det finns vissa begränsade scenarier som kommer att klara kompilatorns analys, men ändå resultera i en körning NullReferenceException.

Sammanfattning

I den här lektionen har du lärt dig att aktivera en nullbar kontext i C# för att skydda mot NullReferenceException. I nästa lektion får du lära dig mer om att uttryckligen uttrycka din avsikt i en nullbar kontext.