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 nullbarer Kontext aktiviert ist. Bevor wir uns mit dem Nullwerte zulassenden Kontext befassen, lassen Sie uns die möglichen Nullwerte zulassenden Typen beschreiben.

Nullwerte zulassende Typen

Vor C# 2.0 ließen nur Verweistypen Nullwerte zu. Werttypen wie int oder DateTimekonnten nichtnullsein. 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 handelt sich um einen 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, Werttypen nullable zu machen. 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 war der Überzeugung, dass alle Verweistypen Nullwerte zulassen“. Da liegen Sie nicht falsch, und ja, sie tun das. Mit diesem Feature können Sie Ihre Absicht ausdrücken, die der Compiler dann durchzusetzen versucht. Die gleiche T?-Syntax drückt aus, dass ein Verweistyp darauf abzielt, nullfähig zu sein.

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 nienull, da es definitiv zugewiesen ist.
  • secondsollte nienull 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.
  • thirdist möglicherweisenull. 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 Nullwerte zulassenden 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.

Nullwerte zulassender Kontext

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

  • 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 Nullwertanalyse durch und gibt keine Warnungen aus, wenn Code null dereferenzieren könnte, aber Sie können Ihren Code trotzdem mithilfe von null-fähigen Verweistypen ? und nullverzeihenden Operatoren ! annotieren.

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 Nullwerte zulassenden Verweistypen

Fügen Sie in der C#-Projektdatei (CSPROJ) dem <Nullable>-Element einen untergeordneten <Project>-Knoten hinzu (oder fügen Sie an eine vorhandene <PropertyGroup> an). Dadurch wird der nullable 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 Nullable-Kontext mit einer Compileranweisung auf den Umfang einer C#-Datei beschränken.

#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 Nullable-Kontext ist in der .csproj Datei in allen C#-Projektvorlagen ab .NET 6.0 und höher standardmäßig 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 Nullable-Kontext analysiert wird.

  1. Die FooBar fooBar = null;-Zeile enthält eine Warnung für die null-Zuweisung: C#-Warnung CS8600: Das NULL-Literal oder ein möglicher NULL-Wert wird in einen Non-Nullable-Typ konvertiert.

    Screenshot der C#-Warnung CS8600: Konvertierung eines Null-Literals oder eines möglichen Nullwerts in einen nicht-nullfähigen 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 nullfähigen Kontext in C# zu aktivieren, um vor NullReferenceException zu schützen. In der nächsten Lerneinheit erfahren Sie mehr über das explizite Ausdrücken Ihrer Absicht in einem Nullwerte zulassenden Kontext.