Ausdrücken der Absicht

Abgeschlossen

In der vorherigen Lerneinheit haben Sie gelernt, wie der C#-Compiler statische Analysen durchführen kann, um einen Schutz vor NullReferenceException aufzubauen. Sie haben außerdem gelernt, wie Sie einen Nullwerte zulassenden Kontext aktivieren. In dieser Einheit erfahren Sie mehr über das explizite Ausdrücken Ihrer Absicht in einem Nullwerte zulassenden Kontext.

Deklarieren von Variablen

Bei aktiviertem Nullable-Kontext erhalten Sie mehr Einblick darin, wie der Compiler Ihren Code sieht. Die Warnungen, die aus einem Kontext mit aktivierter Zulassung von NULL-Werten generiert werden, können umgesetzt werden, und indem Sie dies tun, definieren Sie explizit Ihre Absichten. Lassen Sie uns beispielsweise den FooBar-Code weiter untersuchen und die Deklaration und Zuweisung überprüfen:

// Define as nullable
FooBar? fooBar = null;

Beachten Sie, dass ? zu FooBar hinzugefügt wurde. Dies teilt dem Compiler mit, dass Sie explizit beabsichtigen, dass fooBar Nullwerte zulässt. Wenn Sie nicht beabsichtigen, die Zulassung von Nullwerten für fooBar festzulegen, die Warnung aber trotzdem vermeiden möchten, sehen Sie sich dies hier an:

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

In diesem Beispiel wird der nulltolerante Operator (!) zu null addiert, was den Compiler anweist, dass Sie diese Variable explizit als null initialisieren. Der Compiler gibt keine Warnungen dazu aus, dass dieser Verweis NULL ist.

Eine bewährte Methode besteht in der Zuweisung Ihrer keine Nullwerte zulassenden Variablen bei der Deklaration zu Nicht-null-Werten, falls möglich:

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

Operatoren

Wie in der vorherigen Einheit erläutert, definiert C# mehrere Operatoren, um Ihre Absicht in Bezug auf Nullwerte zulassende Verweistypen auszudrücken.

Nulltoleranter (!) Operator

Den nulltoleranten Operator (!) haben Sie im vorhergehenden Abschnitt kennengelernt. Er weist den Compiler an, die CS8600-Warnung zu ignorieren. Dies ist eine Möglichkeit, dem Compiler mitzuteilen, dass Sie wissen, was Sie tun. Dabei gibt es den Vorbehalt, dass Sie dann auch tatsächlich wissen sollten, was Sie tun!

Wenn Sie Nullwerte nicht zulassende Typen initialisieren, während ein Nullwerte zulassender Kontext aktiv ist, müssen Sie den Compiler möglicherweise explizit um Fehlertoleranz bitten. Beachten Sie z. B. folgenden Code:

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

Im vorstehenden Beispiel generiert FooBar fooBar = fooList.Find(f => f.Name == "Bar"); eine CS8600-Warnung, weil Find möglicherweise null zurückgibt. Diese mögliche null würde fooBar zugewiesen, das in diesem Kontext keine Nullwerte zulässt. In diesem etwas konstruierten Beispiel wissen wir jedoch, dass Find nie null zurückgeben wird, wie bereits dargelegt. Sie können diese Absicht gegenüber dem Compiler mit dem nulltoleranten Operator ausdrücken:

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

Beachten Sie das ! am Ende von fooList.Find(f => f.Name == "Bar"). Dies teilt dem Compiler mit, dass Sie wissen, dass das von der Find-Methode zurückgegebene Objekt nullsein kann und das so in Ordnung ist.

Der NULL-tolerante Operator kann auch vor einem Methodenaufruf oder einer Eigenschaftsauswertung inline auf ein Objekt angewendet werden. Sehen Sie sich ein weiteres konstruiertes Beispiel an:

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

Im vorherigen Beispiel:

  • GetFooList ist eine statische Methode, die einen Nullwerte zulassenden Typ zurückgibt, List<FooBar>?.
  • fooList wird der von GetFooList zurückgegebene Wert zugewiesen.
  • Der Compiler generiert eine Warnung für fooList.Find(f => f.Name == "Bar");, da der fooList zugewiesene Wert null sein kann.
  • Wenn fooList nicht null ist, kann Find möglicherweise null zurückgeben, wir wissen aber, dass dies nicht der Fall ist, sodass der nulltolerante Operator angewendet wird.

Sie können den nulltoleranten Operator auf fooList anwenden, um die Warnung zu deaktivieren:

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

Hinweis

Sie sollten den NULL-toleranten Operator mit Bedacht verwenden. Ihn einfach zu verwenden, um eine Warnung zu verwerfen, bedeutet, dass Sie den Compiler anweisen, Sie nicht bei der Erkennung möglicher Nullfehler zu unterstützen. Verwenden Sie ihn also sparsam und nur dann, wenn Sie sich sicher sind.

Weitere Informationen finden Sie unter !- Operator (NULL-toleranter Operator) (C#-Referenz).

Null-Sammeloperator (??)

Beim Arbeiten mit Typen, die Nullwerte zulassen, müssen Sie möglicherweise bewerten, ob sie aktuell null sind, und bestimmte Maßnahmen ergreifen. Wenn beispielsweise einem Nullable-Typ null zugewiesen wurde oder er nicht initialisiert wurde, müssen Sie ihm möglicherweise einen Wert ungleich null zuweisen. In dieser Situation ist der Null-Sammeloperator (??) nützlich.

Betrachten Sie das folgende Beispiel:

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

    // Safely use salesTax object.
}

Im oben stehenden C#-Code ist Folgendes passiert:

  • Der salesTax-Parameter ist als Nullwerte zulassende IStateSalesTax definiert.
  • Innerhalb des Methodentexts wird die salesTax mithilfe des Null-Sammeloperators bedingt zugewiesen.
    • Dadurch ist sichergestellt, dass bei der Übergabe von salesTax als null einen Wert aufweist.

Tipp

Dies entspricht funktional dem folgenden C#-Code:

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

    // Safely use salesTax object.
}

Nachfolgend sehen Sie ein Beispiel für eine andere gängige C#-Sprache, in der der NULL-Sammeloperator nützlich sein kann:

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ür den C#-Code oben gilt:

  • Definiert eine generische Wrapperklasse, bei der der generische Typparameter auf new()eingeschränkt ist.
  • Der Konstruktor akzeptiert einen T source-Parameter, der den Standardwert null aufweist.
  • Die umschlossene _source wird bedingt mit einem new T() initialisiert.

Weitere Informationen finden Sie unter ??- und ??=-Operatoren (C#-Referenz).

Nullbedingter (?.) Operator

Bei der Arbeit mit Nullable-Typen müssen Sie möglicherweise auf der Grundlage des Zustands eines null-Objekts bedingte Aktionen ausführen. In der vorherigen Einheit wurde beispielsweise der FooBar-Datensatz verwendet, um NullReferenceException durch Dereferenzierung von null zu veranschaulichen. Den Auslöser stellte der Aufruf von ToString dar. Betrachten Sie das gleiche Beispiel, aber diesmal mit Anwendung des nullbedingten Operators:

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ür den C#-Code oben gilt:

  • Führt eine bedingte Dereferenzierung von fooBar durch und weist das Ergebnis ToString der Variablen str zu.
    • Die str-Variable ist vom Typ string? (Zeichenfolge, die Nullwerte zulässt).
  • Der Wert von str wird in die Standardausgabe geschrieben, was Nichts ergibt.
  • Das Aufrufen von Console.Write(null) ist gültig, sodass keine Warnungen ausgegeben werden.
  • Beim Aufruf von Console.Write(str.Length) würden Sie eine Warnung empfangen, da Sie möglicherweise null dereferenzieren würden.

Tipp

Dies entspricht funktional dem folgenden C#-Code:

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

Sie können Operatoren kombinieren, um Ihre Absicht noch weitergehend auszudrücken. Beispielsweise können Sie die Operatoren ?. und ?? verketten:

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

Weitere Informationen finden Sie unter ?. und ?[] (NULL-bedingte Operatoren).

Zusammenfassung

In dieser Lerneinheit haben Sie erfahren, wie Sie Ihre Absicht, Nullwerte zuzulassen, im Code ausdrücken. In der nächsten Lerneinheit wenden Sie das Gelernte auf ein vorhandenes Projekt an.

Überprüfen Sie Ihr Wissen

1.

Was ist der default-Wert des Verweistyps string?

2.

Was ist das erwartete Verhalten bei der Dereferenzierung von null?

3.

Was geschieht, wenn dieser C#-Code throw null; ausgeführt wird?

4.

Welche Anweisung ist in Bezug auf Nullable-Verweistypen am genauesten?