C# 8.0 - Das Ende der NullReferenceException ?

Ach, was wäre doch nur Programmieren ohne dem Lieblingsbug der .NET Entwickler: die NullReferenceException. So gut wie jeder Programmierer hat sie mindestens einmal gesehen und sich gewundert, warum beispielsweise ein bestimmtes Objekt nicht initialisiert ist. Oder schlimmer: Warum eine Methode null anstelle einer Objektreferenz zurückgibt.

Der Wert null ist ein wichtiger Bestandteil des .NET Frameworks, da dies der Standardwert eines jeden Referenztypen ist. Solange man einer Variable eines Referenztypen keinen Wert zuweist, muss man auf irgendeine Art und Weise dessen Abwesenheit im Programmcode repräsentieren. Genau dies wird mit null erreicht.  Ein weiterer häufiger Anwendungsfall sind die sogenannten "Nullable Value Types", bei denen es möglich ist, einem Wertetypen null zuzuweisen. Dies ist insbesondere beim Arbeiten mit Datenbanktabellen praktisch, da in vielen Datenbanken die Abwesenheit eines Wertes in einer Tabelle mit null repräsentiert wird. Diese „Abwesenheit“ eines Wertes kann beispielsweise in einer normalen Int32 Variable nicht ohne weiteres abgebildet werden.
Beispiel: Die Spalte Alter in einer Tabelle hat für die Person X keinen Eintrag. Soll das Alter der Person auf „0“ gesetzt werden, um die Abwesenheit des Wertes in der Datenbank zu repräsentieren ? Wäre „-1“ ein geeigneter Wert, und wenn ja, hat man den Setter des Properties für diesen Sonderfall angepasst? Und wie würde das Problem bei dem Datentyp byte statt Int32 aussehen ? Fragen über Fragen…

Während das Setzen auf null für Wertetypen ein reines Opt-In ist, kommen wir bei Referenztypen oftmals nicht drum herum, null zu verwenden. Standardmäßig kann man in den meisten Fällen aber davon ausgehen, dass die zur Laufzeit verwendeten Objekte initialisiert sind und zur Verwendung zur Verfügung stehen. Ab und zu schleicht sich aber doch ein kleiner Implementierungsfehler ein, der im weiteren Programmablauf zu einer NullReferenceException führt.
Mein persönlicher Favorit ist das unabsichtlich vergessene Instanziieren von Objekten, viele kostbare Minuten sind bei mir schon auf der Suche nach diesem simplen Fehler draufgegangen.

Grundsätzlich kann man in sehr vielen Fällen davon ausgehen, dass ein Objekt zum Zeitpunkt der Verwendung initialisiert ist und uns zur Verfügung steht. Gelegentlich kann es aber auch vorkommen, dass wir das nicht-Vorhandensein eines Objektes durch den Wert null repräsentieren müssen. Ein (leider) beliebtes Antipattern: Eine Methode, die, aus welchem Grund auch immer, null anstelle der in der Signatur definierten Objektinstanz zurückliefert.
Wenn man sich bewusst auf dieses Antipattern einlässt, muss man im weiteren Programmablauf dauernd prüfen, ob die Rückgabe eine funktionierende Objektreferenz oder doch null ist. Der Code wird dann meist mit lauter Verzweigungen zugepflastert, die auf null prüfen. Die Einführung des "Null-Conditional-Operators" in C# 6.0 hat diese Situation glücklicherweise ein wenig verbessert.

Doch was kann man nun tun ? Gänzlich auf die Verwendung von Referenztypen verzichten ? Null komplett verbieten ? Die funktionale Programmiersprache F# hat hierfür einen interessanten Ansatz:
Alles ist per default Immutable und null ist als regulärer Wert nicht zugelassen.
Für den „Es ist kein Wert vorhanden“ – Fall kann man beispielsweise "Option-Types" verwenden:
Die Rückgabe einer Funktion kann für den Datentyp T „Some(T)“ oder „None“ sein. Mithilfe von Pattern-Matching ist der Programmierer dann in weiterer Folge dazu gezwungen, beide Fälle zu behandeln. Ein erzwungener Null-Check sozusagen. Schauen wir uns das einmal genauer an:

Der folgende C#-Code kann ohne Probleme kompilieren, wird aber zur Laufzeit eine NullReferenceException werfen. Das liegt daran, dass null ein gültiger Wert für einen String ist.


In F# hingegen können wir null nicht direkt übergeben. Um einen „fehlenden Wert“ zu repräsentieren, verwendet man sogenannte Option-Types. Die Möglichkeiten hierfür wären "Some" (= ein Wert ist vorhanden) und "None" (= kein Wert vorhanden). Pointer oder „nicht initialisierte Variablen“ gibt es in der funktionalen Sprache F# nicht.

Beim direkten Zuweisen von "None" merken wir sofort, dass der Compiler das Auslesen der Length-Eigenschaft nicht zulässt.

Wenn wir also mit dem String arbeiten wollen, müssen wir das sogenannte Pattern-Matching verwenden:

Hier sehen wir, dass wir den Wert des Option-Types durch das Pattern-Matching sauber herausbekommen: Wenn in der Variable "text2" ein Wert vorhanden ist, greift der „Some“ – Fall und die Länge des Strings wird ermittelt. Falls kein Wert vorhanden ist, greift der „None“ – Fall und es wird 0 zurückgegeben.

Wo liegt nun aber der Unterschied zu C# ? Dasselbe Problem können wir in C# auch mit folgendem Code lösen:

In C# ist ein „null String“ vom Datentyp her immer noch ein String. Zum Kompilierzeitpunkt kann daher nicht unterschieden werden, ob eine gültige Zeichenkette wie „Hallo Welt“ oder null drinnensteht. Erst zur Laufzeit bekommen wir im schlimmsten Fall eine NullReferenceException.
Wir können natürlich wie im obrigen Beispiel dauernd auf null prüfen und Alternativwerte angeben, doch das ist komplett optional. Wenn wir in unserer Businesslogik nur ein paar Nullchecks unabsichtlich vergessen, könnte es irgendwann einmal zu einer NullReferenceException kommen.

In F# hingegen würden man beispielsweise Option-Types nutzen. Mit denen hat man den Vorteil, dass man nicht direkt auf Properties wie Length oder Methoden des darunterliegenden Strings zugreifen kann. Somit muss man mithilfe des Pattern-Matchings den Wert sozusagen erst „auspacken“, ein erzwungener Null-Check. Alle potentiellen Fehler oder Versäumnisse bekommen wir also entweder in der IDE oder beim kompilieren mit.

 

C# 8.0

Wozu also nun die Gegenüberstellung von C# und F# ? Aktuell wird für die nächste große Sprachversion von C# an einem Feature gearbeitet, welches das Problem der ständigen NullChecks lösen könnte: „Nullable ReferenceTypes“
Hierbei wird das Konzept der NullableTypes, welches bei Wertetypen bereits existiert, auch auf Referenztypen ausgedehnt: Ein Referenztyp darf u.U nicht mehr null sein !

Eine möglicher Anwendungsfall könnte beispielsweise so aussehen:

Im Vergleich zu F# bekommt man hier aktuell nur Warnungen, wenn zum Zeitpunkt X eine Variable null sein könnte. Insbesondere in Großprojekten kann dieses Feature durchaus praktisch sein. Anfangs wird man sicher bei der Umstellung mit Warnungen überflutet, doch die eine oder andere Warnung könnte doch zu einer Stelle führen, wo man unerlaubterweise null als Wert verwendet hat.

Und jeder Bug weniger bringt uns alle einen großen Schritt weiter zu sauberem Code und fehlerfreien Projekten !


Für detailiertere Informationen und eine Preview des geplanten Features empfehle ich folgende GitHub - Seite: The C# Nullable Reference Types Preview

Wer mehr über die vielen Möglichkeiten von C# und den neuen Sprachfeatures erfahren möchte, kann auch einen passenden Kurs dazu besuchen.

Kommentare sind geschlossen