Die Veröffentlichung von C# 11 mit .NET 7 im November 2022
liegt inzwischen bereits eine Weile zurück.
Weil .NET 7 nur mit Standard Term Support (STS) angeboten wird, also Fehlerkorrekturen und Sicherheitsverbesserungen lediglich für 18 Monate erhält, haben jedoch viele Projekte die Version ausgesetzt.
Mit .NET 8 kam im November 2023
wieder eine Long Term Support (LTS) Veröffentlichung (drei Jahre Unterstützung), sodass einige neue .NET- und C#-Funktionalitäten für den breiten Einsatz in Anwendungen verfügbar wurden. Eine kleine, allerdings sehr willkommene Neuerung ist das required
-Schlüsselwort für C# 11 und höher, das hier kurz präsentiert wird.
Zur Erläuterung dient im Folgenden das einfache Datentransferobjekt Customer
mit zwei Eigenschaften:
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Motivation
Funktional gibt es an der Customer
-Klasse nichts auszusetzen:
Daten können einfach zugewiesen und ausgelesen werden, Serialisierung und Mapping sind mit gängigen Werkzeugen problemlos möglich und falls es notwendig werden sollte, könnten die automatischen Getter und Setter mit Eigenimplementierungen ersetzt werden, ohne Anpassungen an anderen Stellen zu erfordern.
Dennoch kann mehr zur Unterstützung der Entwickler und zur Vermeidung von Fehlern getan werden. Beispielsweise kann die Zuweisung eines Wertes für eine Eigenschaft vergessen werden. Insbesondere wenn der Customer
um neue Eigenschaften ergänzt wird, ist es leicht, eine Verwendung der Klasse unbeabsichtigt zu übergehen.
Ein zusätzlicher Effekt tritt ein, wenn die Klasse in einem Nullable-Kontext
definiert wird:
In einem solchen Kontext darf die Name
-Eigenschaft vom Typ string
nicht den Wert null
annehmen. Eine Limitierung, die bei der Vermeidung der weit verbreiteten NullReferenceException
hilft.
Weil der Standardwert für Zeichenketten, abrufbar über default(string)
, jedoch null
ist, löst der Compiler in obiger Customer
-Klasse eine Meldung aus:
warning CS8618: Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
Diese Meldung (Nonnullable reference not initialized
) ist ohne weitere Konfiguration eine Warnung und sollte beachtet werden, um die Vorteile eines Nullable-Kontextes in Anspruch nehmen zu können.
Die einfache Lösung durch einen Wechsel des Datentyps der Name
-Eigenschaft zu string?
ist häufig nicht sinnvoll, weil so bei jeder Verwendung zunächst auf null
geprüft werden müsste, obwohl der Fehler eher an anderen Code-Stellen zu suchen ist (vorausgesetzt null
stellt keinen validen Wert dar).
Alternativ könnte die Eigenschaft direkt mit einem gültigen Wert in der Customer
-Klasse initialisiert werden:
class Customer
{
public int Id { get; set; }
public string Name { get; set; } = "(unknown)";
}
Wie hier direkt ersichtlich ist, gibt es in vielen Konstellationen keinen allgemeingültigen Standardwert.
Eine weitere Möglichkeit wäre die Verwendung eines Konstruktors:
class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public Customer(int id, string name)
{
Id = id;
Name = name;
}
}
Zwar muss bei der Konstruktor-Variante kein Standardwert festgelegt werden und es kann überdies sichergestellt werden, dass alle Pflichtfelder einen Wert erhalten. Diese Lösung wird allerdings bei größeren Datentransferobjekten sehr umständlich (für Konsumenten und die Customer
-Klasse) und ist nicht (oder nicht ohne Weiteres) mit manchen Fremdbibliotheken kompatibel, die Instanzen anlegen müssen.
required
als neue Option
Das required
-Schlüsselwort ist für solche Fälle eine charmante Lösung:
class Customer
{
public required int Id { get; set; }
public required string Name { get; set; }
}
Anmerkung: Zum Zeitpunkt der Artikelveröffentlichung kennt unser Blog-System required
noch nicht als Schlüsselwort und gibt es deswegen leider nicht als solches wieder.
Mit required
gekennzeichnete Eigenschaften und Felder müssen bei Instanziierung der sie enthaltenden Klasse explizit einen Wert über einen Objektinitialisierer
erhalten (es gibt Sonderlösungen für Konstruktoren; hierfür sei auf die Dokumentation
verwiesen).
Entsprechend kann von der um die required
-Schlüsselwörter erweiterte Definition der Customer
-Klasse nur noch eine Instanz erstellt werden, wenn per Objektinitialisierer Id
und Name
einen Wert erhalten:
Customer largestCustomer = new()
{
Id = 8192,
Name = "Contoso Ltd",
};
Der zugewiesene Wert darf dem Standardwert (für Referenzdatentypen null
) entsprechen, sodass required
keine Einschränkung des Datentyps darstellt, aber Entwickler zu bewussten Initialisierungsentscheidungen zwingt. Im Beispiel dürfte demnach der Id
-Eigenschaft der Standardwert für Zahlen 0
zugewiesen werden.
Fehlt bei der Objekterstellung eine Wertzuweisung…
Customer largestCustomer = new()
{
Id = 8192,
};
…akzeptiert der Compiler dies nicht und produziert eine Fehlermeldung:
error CS9035: Required member 'Customer.Name' must be set in the object initializer or attribute constructor.
Auch die Validierung für Nullable-Kontexte wurde um Unterstützung für das required
-Schlüsselwort erweitert, sodass so markierte Felder und Eigenschaften ohne Standardinitialisierer definiert werden dürfen, selbst wenn es Referenzdatentypen sind. Für Customer.Name
wird also keine Meldung mehr ausgegeben.
Bei der JSON-Deserialisierung mit System.Text.Json
wird das required
-Schlüsselwort ebenfalls beachtet (Dokumentation
).
Fazit zum required-Schlüssenwort von C#
Mit dem required
-Schlüsselwort hat C# eine neue Funktionalität erhalten, die Entwicklern die Arbeit erleichtern und gelegentlich sogar Fehler verhindern kann. Dabei ist die Verwendung simpel und erfordert keine kryptische Syntax.
Des Weiteren wird ein größerer Störfaktor beim Einsatz von Nullable-Kontexten ausgeschaltet, sodass diese deutlich seltener als bisher bei neuen Projekten deaktiviert werden dürften.
Insgesamt ist das Schlüsselwort eine gelungene Spracherweiterung, die voraussichtlich insbesondere bei Datentransferobjekten weite Verwendung finden wird.