Grundlegendes zur Nullzulässigkeit

Abgeschlossen

Wenn Sie .NET-Entwickler sind, ist die Wahrscheinlichkeit hoch, dass Ihnen die System.NullReferenceException bereits begegnet ist. Diese tritt zur Laufzeit auf, wenn eine null dereferenziert wird – also, wenn eine Variable zur Laufzeit ausgewertet wird, die Variable jedoch auf null verweist. Diese Ausnahme ist die bei weitem am häufigsten auftretende Ausnahme innerhalb des .NET-Ökosystems. Der Schöpfer von null, Sir Tony Hoare, bezeichnet null als den „Milliarden-Dollar-Fehler“.

Im folgenden Beispiel wird die FooBar-Variable null zugewiesen und sofort dereferenziert, wodurch das Problem offen zutage tritt:

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

Das Problem wird für Entwickler viel schwieriger zu erkennen, wenn Ihre Apps größer und komplexer werden. Das Aufspüren potenzieller Fehler wie dieses ist eine Aufgabe für Tools, und der C#-Compiler steht bereit, um zu helfen.

Definieren von Nullsicherheit

Der Begriff Nullsicherheit definiert eine Reihe von Merkmalen, die für Nullable-Typen spezifisch sind, mit denen sich die Anzahl der möglichen NullReferenceException-Vorkommen verringern lässt.

Beim Betrachten des vorstehenden FooBar-Beispiels wird klar, dass die NullReferenceException vermieden werden könnte, wenn vor der Dereferenzierung überprüft würde, ob die fooBar-Variable den Wert null hat:

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

Zur Unterstützung bei der Identifizierung von Szenarien wie diesem kann der Compiler die Absicht Ihres Codes ableiten und das gewünschte Verhalten erzwingen. Dies gilt jedoch nur, wenn ein Nullwerte zulassender Kontext aktiviert ist. Bevor wir uns mit dem Nullwerte zulassenden Kontext befassen, lassen Sie uns die möglichen Nullable-Typen beschreiben.

Nullable-Typen

Vor C# 2.0 ließen nur Verweistypen Nullwerte zu. Werttypen wie int oder DateTime können nicht nullsein. Wenn diese Typen ohne Wert initialisiert werden, greifen sie auf ihren default-Wert zurück. Im Fall von eines int ist dies 0. Bei einem DateTime ist dies DateTime.MinValue.

Verweistypen, die ohne Anfangswerte instanziiert werden, funktionieren anders. Der default-Wert für alle Verweistypen ist null.

Betrachten Sie den folgenden C#-Codeausschnitt:

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

Im vorherigen Beispiel:

  • first ist null, weil der Verweistyp string deklariert, aber keine Zuweisung vorgenommen wurde.
  • second wird string.Empty bei der Deklaration zugewiesen. Für das Objekt gab es zu keiner Zeit eine null-Zuweisung.
  • third ist 0, obwohl keine Zuweisung erfolgt ist. Es ist eine struct (Werttyp) und hat den default-Wert 0.
  • date ist nicht initialisiert, aber sein default-Wert ist System.DateTime.MinValue.

Seit C# 2.0 können Sie Nullable-Werttypen mit Nullable<T> (oder T? kurz) definieren. Dies ermöglicht es, dass Werttypen Nullwerte zulassen. Betrachten Sie den folgenden C#-Codeausschnitt:

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

Im vorherigen Beispiel:

  • first ist null, da der Nullable-Werttyp nicht initialisiert ist.
  • second wird null bei der Deklaration zugewiesen.
  • third ist null, da der default-Wert für Nullable<int>null lautet.
  • fourth ist 0, da der Ausdruck new() den Nullable<int>-Konstruktor aufruft und int standardmäßig 0 ist.

In C# 8.0 wurden Nullable-Verweistypen eingeführt, in denen Sie Ihre Absicht ausdrücken können, dass ein Verweistyp möglicherweisenull ist oder immer Nicht-null ist. Sie denken vielleicht: „Ich hatte das so verstanden, dass alle Verweistypen Nullwerte zulassen.“ Das stimmt auch. Mit diesem Feature können Sie Ihre Absicht ausdrücken, die der Compiler dann durchzusetzen versucht. Die gleiche T?-Syntax gibt an, dass ein Verweistyp Nullwerte zulassend sein soll.

Betrachten Sie den folgenden C#-Codeausschnitt:

#nullable enable

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

Unter Zugrundelegung des vorherigen Beispiels leitet der Compiler Ihre Absicht wie folgt ab:

  • first ist nie null, da es definitiv zugewiesen ist.
  • second sollte nie null sein, obwohl es anfänglich null ist. Das Auswerten von second vor dem Zuweisen eines Werts führt zu einer Compilerwarnung, da es nicht initialisiert ist.
  • third ist möglicherweise null. Beispielsweise verweist es möglicherweise auf einen System.String, möglicherweise aber auch auf null. Beide dieser Varianten sind akzeptabel. Der Compiler unterstützt Sie, indem er Sie warnt, wenn Sie third dereferenzieren, ohne zuerst zu überprüfen, ob er nicht null ist.

Wichtig

Um das Feature der Nullable-Verweistypen wie oben gezeigt zu verwenden, müssen sie sich innerhalb eines Nullwerte zulassenden Kontexts befinden. Dies wird im nächsten Abschnitt ausführlich beschrieben.

Nullable-Kontext

Nullable-Kontexte ermöglichen eine differenzierte Steuerung der Interpretation von Verweistypvariablen durch den Compiler. Es gibt vier mögliche Kontexte, die Nullwerte zulassen:

  • disable: Der Compiler verhält sich ähnlich wie in C# 7.3 und früheren Versionen.
  • enable: Der Compiler aktiviert alle Nullverweisanalysen und alle Sprachfeatures.
  • warnings: Der Compiler führt alle Nullverweisanalysen durch und gibt Warnungen aus, wenn der Code möglicherweise null dereferenziert.
  • annotations: Der Compiler führt keine Nullverweisanalyse durch und gibt keine Warnungen aus, wenn Code null dereferenzieren könnte, Sie können Ihren Code aber trotzdem mithilfe von Nullwerte zulassenden Verweistypen ? und nulltoleranten Operatoren ! kommentieren.

Dieses Modul ist auf den Umfang der Nullwerte zulassenden Kontexte disable oder enable festgelegt. Weitere Informationen finden Sie unter Nullable-Verweistypen: Nullable-Kontexte.

Aktivieren von Nullable-Verweistypen

Fügen Sie in der C#-Projektdatei (CSPROJ) dem <Project>-Element einen untergeordneten <Nullable>-Knoten hinzu (oder fügen Sie an eine vorhandene <PropertyGroup> an). Dadurch wird der Nullwerte zulassend e Kontext enable auf das gesamte Projekt angewendet.

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

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

    <!-- Omitted for brevity -->

</Project>

Alternativ können Sie den Umfang des Nullable-Kontexts mithilfe einer Compileranweisung auf eine C#-Datei anwenden.

#nullable enable

Die vorstehende C#-Compileranweisung entspricht funktional der Projektkonfiguration, ihr Umfang ist jedoch auf die Datei beschränkt, in der sie sich befindet. Weitere Informationen finden Sie unter Nullable-Verweistypen: Nullable-Kontexte (Dokumentation).

Wichtig

Der Nullwerte zulassende Kontext ist in der CSPROJ-Datei standardmäßig in allen C#-Projektvorlagen ab .NET 6.0 und höher aktiviert.

Wenn der Nullwerte zulassende Kontext aktiviert ist, erhalten Sie neue Warnungen. Betrachten Sie das vorangehende FooBar-Beispiel – es enthält zwei Warnungen, wenn es in einem Kontext analysiert wird, in dem Nullwerte zugelassen werden:

  1. Die FooBar fooBar = null;-Zeile enthält eine Warnung für die null-Zuweisung: C# Warning CS8600: Converting null literal or possible null value to non-nullable type. (C#-Warnung CS8600: Konvertieren eines Null-Literals oder eines möglichen Nullwerts in einen non-Nullable-Typ).

    Screenshot der C# Warnung CS8600: Konvertieren eines Null-Literals oder eines möglichen Nullwerts in einen non-Nullable-Typ

  2. Die _ = fooBar.ToString();-Zeile enthält ebenfalls eine Warnung. Dieses Mal hat der Compiler Bedenken, dass fooBar möglicherweise null ist: C# Warning CS8602: Dereference of a possibly null reference (C#-Warnung CS8602: Dereferenzierung eines potenziellen Nullverweises).

    Screenshot der C#-Warnung CS8602: Dereferenzierung eines potenziellen Nullverweises

Wichtig

Es gibt keine garantierte Nullsicherheit, selbst wenn Sie auf alle Warnungen reagieren und diese beseitigen. Es gibt einige eingeschränkte Szenarios, die die Analyse des Compilers bestehen und dennoch zu einer NullReferenceException zur Laufzeit führen.

Zusammenfassung

In dieser Lerneinheit haben Sie gelernt, einen Nullwerte zulassenden Kontext in C# zu aktivieren, um einen Schutz vor NullReferenceException aufzubauen. In der nächsten Lerneinheit erfahren Sie mehr über das explizite Ausdrücken Ihrer Absicht in einem Nullwerte zulassenden Kontext.