Nullable-Typen
Nullable-Typen gab es zwar schon in Visual Basic 2005, aber sie waren nur – sagen wir einmal – halbherzig in Visual Basic implementiert. Zwar erfuhren sie schon eine (notwendige!) Sonderbehandlung durch die CLR (siehe Abschnitt »Besonderheiten bei Nullable beim Boxen« ab Seite 41), aber anders als in C# waren sie noch nicht mit einem eigenen Typliteral implementiert. Das hat sich geändert.
Nullable ist ein generischer Datentyp mit einer Einschränkung auf Wertetypen. Er ermöglicht, dass ein beliebiger Wertetyp neben seiner eigentlichen Werteart einen weiteren Zustand »speichern« kann – nämlich Nothing.
Generics gibt es zwar schon seit der Version 2005, jedoch sind sie vielen Entwicklern noch nicht geläufig. Diesem Thema ist deswegen ein eigenes Kapitel, nämlich das Kapitel 6 gewidmet.
Ist das wichtig? Oh ja! Beispielsweise in der Datenbankprogrammierung. Wenn Sie bereits Erfahrungen in der Datenbankprogrammierung haben, wissen Sie auch sicherlich, dass Ihre Datenbanktabellen über Datenfelder verfügen können, die den »Wert« Null »speichern« können – als Zeichen dafür, dass eben nichts (auch nicht die Zahl 0) in diesem Feld gespeichert wurde.
Ein anderes Beispiel sind CheckBox-Steuerelemente in Windows Forms-Anwendungen: Sie verfügen über einen Zwischenzustand, der den Zustand »nicht definiert« anzeigen soll. Eine einfache boolesche Variable könnte alle möglichen Zustände nicht aufnehmen – True und False sind dafür einfach zu wenig. Anders ist es, wenn Sie eine Variable vom Typ Boolean definieren könnten, die auch den »Nichts-ist-gespeichert«-Wert widerspiegeln könnte.
Abbildung 3.6 Ein Boolean eignet sich auch dazu, Zwischenzustände
eines CheckBox-Steuerelements zu speichern
Und das geht: In Visual Basic 2008 definiert man eine primitive Variable als Nullable-Datentyp, indem man ihrem Bezeichner oder dem Bezeichner für den Datentyp ein Fragezeichen anhängt. Das sieht entweder so …
Private myCheckBoxZustand As Boolean?
… oder so aus:
Private myCheckBoxZustand? As Boolean
In Visual Basic 2005 war es notwendig, Nullables auf folgende Weise zu definieren:
Private myCheckBoxZustand As Nullable(Of Boolean)
Aber keine Angst: Sie müssen sich jetzt nicht durch hunderte, vielleicht tausende Zeilen Code hangeln und für Visual Basic 2008 die entsprechenden Änderungen mit dem Fragezeichen vornehmen. Die umständlichere ältere Variante behält natürlich nach wie vor ihre Gültigkeit.
Unter .\Samples\Chapter03 - NeuInCompiler\NullableUndCheckbox finden Sie das folgende Beispielprojekt.
Dieses Beispiel demonstriert, wie alle Zustände eines CheckBox-Steuerelements, dessen ThreeState-Eigenschaft zur Anzeige aller drei Zustände auf True gesetzt wurde, in einer Member-Variablen vom Typ Boolean? gespeichert werden können. Klicken Sie beim Ausführen der Anwendung auf Zustand speichern, um den Zustand des CheckBox-Steuerelements in der Member-Variablen zu sichern, verändern Sie anschließend den Zustand, und stellen Sie den ursprünglichen Zustand des CheckBox-Steuerelements mit der entsprechenden Schaltfläche wieder her.
Der entsprechende Code dazu lautet folgendermaßen:
Public Class Form1
Private myCheckBoxZustand As Boolean?
'Das ginge auch:
'Private myCheckBoxZustand? As Boolean
'Und das auch:
'Private myCheckBoxZustand As Nullable(Of Boolean)
Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _
btnOK.Click
Me.Close()
End Sub
Private Sub btnSpeichern_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _
btnSpeichern.Click
If chkDemo.CheckState = CheckState.Indeterminate Then
myCheckBoxZustand = Nothing
Else
myCheckBoxZustand = chkDemo.Checked
End If
End Sub
Private Sub btnWiederherstellen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles btnWiederherstellen.Click
If Not myCheckBoxZustand.HasValue Then
chkDemo.CheckState = CheckState.Indeterminate
Else
If myCheckBoxZustand.Value Then
chkDemo.CheckState = CheckState.Checked
Else
chkDemo.CheckState = CheckState.Unchecked
End If
End If
End Sub
End Class
Die Zeilen, in denen die Member-Variable Boolean? zum Einsatz kommt, sind im Listing fett markiert. Dabei fällt Folgendes auf:
- Wertzuweisung: Wenn Sie einen Wert des zugrunde liegenden Typs an type? zuweisen wollen, können Sie die implizite Konvertierung verwenden, den entsprechenden Wert also direkt zuweisen, etwa wie in der Zeile
myCheckBoxZustand = chkDemo.Checked
zu sehen.
- Auf Nothing zurücksetzen: Möchten Sie eine Nullable-Instanz auf Nothing zurücksetzen, weisen Sie ihr einfach den »Wert« Nothing zu – wie im Listing an dieser Stelle zu sehen:
myCheckBoxZustand = Nothing
- Auf Wert prüfen: Möchten Sie wissen, ob eine Nullable-Instanz einen Wert oder Nothing enthält, verwenden Sie deren Eigenschaft HasValue. Auch dafür gibt es ein Beispiel im Listing:
If Not myCheckBoxZustand.HasValue Then
chkDemo.CheckState = CheckState.Indeterminate
Else
.
.
.
- Wert abrufen: Und schließlich müssen Sie natürlich auch den Wert, den eine Nullable-Instanz trägt, wenn sie nicht Nothing ist, ermitteln können. Dazu dient die Eigenschaft Value. Ein Beispiel dafür:
If myCheckBoxZustand.Value Then
chkDemo.CheckState = CheckState.Checked
Else
chkDemo.CheckState = CheckState.Unchecked
End If
.
.
.
Erst in diesem Beispiel fiel auf, dass man offensichtlich ein CheckBox-Steuerelement, dessen ThreeState-Eigenschaft gesetzt ist und das momentan den Intermediate-Zustand trägt, nicht mit seiner Checked-Eigenschaft in einen anderen Zustand versetzen kann (Checked oder Unchecked). Sie können in diesem Fall nur die CheckState-Eigenschaft verwenden, um das CheckBox-Steuerelement programmgesteuert wieder aus dem Intermediate-Zustand herauszuholen!
Besonderheiten bei Nullable beim Boxen
Der Datentyp Nullable (type?) ist das, was man in Visual Basic als Struktur programmieren würde, also ein Wertetyp. Doch Sie könnten diesen Wertetyp nicht 1:1 nachprogrammieren, denn er erfährt durch die Common Language Runtime eine besondere Behandlung – und das ist auch gut so.
Unter .\Samples\Chapter03 - NeuInCompiler\NullableDemo finden Sie das folgende Beispielprojekt.
Wenn Sie eine Instanz einer beliebigen Struktur – also eines beliebigen Wertetyps – verarbeiten, kommt irgendwann der Zeitpunkt, an dem Sie diesen Wertetyp in einer Objektvariablen boxen müssen – beispielsweise wenn Sie ihn als Bestandteil eines Arrays oder einer Auflistung (Collection) speichern.
Wann immer Sie einen definierten Wertetyp in einem Objekt boxen, kann dieses Objekt logischerweise nicht Nothing sein, ganz egal, welchen »Wert« diese Struktur hat. Im Falle des Nullable-Typs ist das anders, wie das folgende Beispiel zeigt:
Module NullableDemo
Sub Main()
Dim locObj As Object
Dim locNullOfInt As Integer? = Nothing
'Es gibt natürlich eine verwendbare Instanz, denn
'Integer? ist ein Wertetyp!
Console.WriteLine("Hat locNullOfInt einen Wert: " & locNullOfInt.HasValue)
'Und dennoch ergibt das folgende Konstrukt True,
'als würde locObj keine Referenz haben!
locObj = locNullOfInt
Console.WriteLine("Ist locObj Nothing? " & (locObj Is Nothing).ToString)
Console.WriteLine()
'Und auch das "Entboxen" geht!
'Es gibt keine Null-Exception!
locNullOfInt = DirectCast(locObj, Integer?)
'Und geht das dann auch? - Natürlich!
locNullOfInt = DirectCast(Nothing, Integer?)
'Und noch weiter. Wir boxen einen Integer?
locNullOfInt = 10
'
locObj = locNullOfInt
Dim locInt As Integer = DirectCast(locObj, Integer)
Console.WriteLine("Taste drücken zum Beenden!")
Console.ReadKey()
'Das geht übrigens nicht, obwohl Nullable die
'Contraints-Einschränkung im Grunde genommen erfüllt!
'Dim locNullOfInt As Nullable(Of Integer?)
End Sub
End Module
Wenn Sie dieses Beispiel ausführen, gibt es die folgenden Zeilen aus:
Hat locNullOfInt einen Wert: False
Ist locObj Nothing? True
Taste drücken zum Beenden!
Das, was hier passiert, ist beileibe keine Selbstverständlichkeit – aber dennoch sauberes Design der CLR, denn: Zwar wird locNullOfInt nicht initialisiert (oder, um es in diesem Beispiel deutlich zu machen, mit Nothing – aber das kommt auf dasselbe raus), aber natürlich existiert dennoch eine Instanz der Struktur. Sie spiegelt eben nur den Wert Nothing wider. Gemäß den bekannten Regeln müsste das anschließende Boxen in der Variablen locObj auch ergeben, dass locObj einen Zeiger auf eine Instanz der Nothing widerspiegelnden locNullOfInt enthält und keinen Null-Zeiger. Doch das ist nicht der Fall, denn die anschließende Ausgabe von
Console.WriteLine("Ist locObj Nothing?" & (locObj Is Nothing).ToString)
zeigt
Ist locObj Nothing? True
auf dem Bildschirm an.
Das »zurückcasten« von Nothing in einen Nullable ist damit natürlich genauso gestattet, wie ebenfalls im Listing zu sehen. Und noch eine Unregelmäßigkeit erfahren Nullables, nämlich wenn es darum geht, einen geboxten Typ (vorausgesetzt er ist eben nicht Nothing) in seinen Grundtyp zurückzucasten, wie der folgende Codeausschnitt zeigt:
'Und noch weiter. Wir boxen einen Integer?
locNullOfInt = 10
'
locObj = locNullOfInt
Dim locInt As Integer = DirectCast(locObj, Integer)
Hier wird ein Nullable-Datentyp in einem Objekt geboxt, aber später zurück in seinen Grunddatentypen gewandelt. Ein, wie ich finde, logisches Design, was allerdings dem »normalen« Vorgehen beim Boxen von Wertetypen in Objekten völlig widerspricht.
Kleine Anekdote am Rande: Dieses Verhalten ist erst zu einem sehr, sehr späten Zeitpunkt beim Entwickeln von Visual Studio 2005 und dem .NET Framework in die CLR eingebaut worden und hat für erhebliche Mehrarbeit bei allen Entwicklerteams und viel zusätzlichen Testaufwand gesorgt. Dass sich Microsoft dennoch für das nunmehr implementierte Verhalten entschieden hat, geht nicht zuletzt auf das Drängen von Kunden und Betatestern zurück, die das Design mit der ursprünglichen, »normalen« CLR-Behandlung von Nullables nicht akzeptieren konnten und als falsch erachteten.
|