Ausdrücken der Absicht
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 null
sein 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 vonGetFooList
zurückgegebene Wert zugewiesen.- Der Compiler generiert eine Warnung für
fooList.Find(f => f.Name == "Bar");
, da derfooList
zugewiesene Wertnull
sein kann. - Wenn
fooList
nichtnull
ist, kannFind
möglicherweisenull
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 zulassendeIStateSalesTax
definiert. - Innerhalb des Methodentexts wird die
salesTax
mithilfe des Null-Sammeloperators bedingt zugewiesen.- Dadurch ist sichergestellt, dass bei der Übergabe von
salesTax
alsnull
einen Wert aufweist.
- Dadurch ist sichergestellt, dass bei der Übergabe von
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 Standardwertnull
aufweist. - Die umschlossene
_source
wird bedingt mit einemnew 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 ErgebnisToString
der Variablenstr
zu.- Die
str
-Variable ist vom Typstring?
(Zeichenfolge, die Nullwerte zulässt).
- Die
- 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.