Datentypen für Datum und Zeit mit Unterstützung für Nullwerte

Einleitung

Die Klassenbibliothek des .NET Frameworks stellt den Strukturdatentyp DateTime (in Visual Basic .NET Date) zur Speicherung und Manipulation von Datums- und Zeitangaben zur Verfügung. Dieser Datentyp besitzt jedoch den Nachteil, daß er nicht in der Lage ist, Nullwerte aufzunehmen. Nullwerte zeigen das Fehlen eines gültigen Wertes an. In der Praxis kommt es oft vor, daß Datumsangaben nicht bekannt sind, etwa die Geburtsdaten von Mitarbeitern oder Zeitpunkte, an denen Ereignisse stattfinden. Während die Speicherung von Nullwerten in Datenbanken kein Problem darstellt, muß im Programmcode anstelle von DateTime ein Datentyp benutzt werden, der auch Nullwerte aufnehmen kann. Im Folgenden werden mehrere Möglichkeiten zur Unterstützung von Nullwerten bei Datumsangaben betrachtet.

Boolesche Variablen zur Anzeige von Nullwerten

Die einfachste, aber zugleich aufwendigste Möglichkeit zur Kennzeichnung von Nullwerten ist das Mitführen einer Booleschen Variablen, die anzeigt, ob der in einer Variablen des Typs DateTime gespeicherte Datumswert gültig ist oder nicht. Nachteilig an dieser Lösung ist, daß Datumsvariable und Boolesche Variable nicht fest aneinander gebunden sind, obwohl sie eine Entität darstellen. Quellcode wird so schnell in seiner Wartbarkeit reduziert, insbesondere dann, wenn mehrere Datumswerte verwaltet werden müssen. Folgendes Listing zeigt ein Beispiel für die Implementierung dieser Methode.

Private m_Birthday As Date
Private m_BirthdayIsNull As Boolean

⋮

If m_BirthdayIsNull Then
    Me.LabelBirthday.Text = "(unbekannt)"
Else
    Me.LabelBirthday.Text = CStr(m_Birthday)
End If

Interpretation eines bestimmten Datumswerts als Nullwert

Eine weitere Lösung, die häufig genannt wird, liegt darin, einen bestimmten, fest gewählten, im Datentyp DateTime darstellbaren Datumswert als Nullwert zu interpretieren. Meist stellen dabei die Datumswerte DateTime.MinValue oder DateTime.MaxValue den Nullwert dar. Der Vorteil dieser Lösung ist die Einfachheit der Implementierung, so werden keine zusätzlichen Variablen oder Klassen benötigt. Nachteilig erweist sich jedoch, daß ein gültiger Datumswert zur Darstellung eines ungültigen Wertes herangezogen wird. Das ist etwa dann problematisch, wenn ein derartiger Datumswert in der Objektschnittstelle einer wiederverwendbaren Bibliothek enthalten und die spezielle Bedeutung eines bestimmten Wertes des Typs DateTime nicht für diesen Datentyp dokumentiert ist. Zudem erlauben Steuerelemente wie das DateTimePicker-Steuerelement die Auswahl des als Nullwert interpretierten Datumswerts, sofern der Auswahlbereich nicht eingeschränkt wurde.

Private m_Birthday As Date

⋮

If m_Birthday = Date.MinValue Then
    Me.LabelBirthday.Text = "(unbekannt)"
Else
    Me.LabelBirthday.Text = CStr(m_Birthday)
End If

Kapseln und erweitern des Datentyps DateTime

Während die ersten beiden vorgestellten Möglichkeiten wenig praxistauglich sind und nur der Vollständigkeit halber in diesen Artikel aufgenommen wurden, beschreiben die folgenden Abschnitte Lösungswege, die sich in verschiedenen Szenarien als vorteilhaft erweisen. Die Idee hinter dieser Methode ist, den Datentyp DateTime um eine Eigenschaft zu erweitern, die anzeigt, ob das Objekt einen Nullwert enthält oder nicht. Der Datentyp DateTime ist jedoch als Strukturtyp implementiert und kann deshalb nicht durch Vererbung erweitert werden. Stattdessen wird ein neuer Datentyp NullableDateTime erstellt, der, in Anlehnung an die Implementierung des Typs DBNull, in der Eigenschaft Value den Datumswert aufnimmt und über den Wert der Eigenschaft IsNull einen Nullwert anzeigt. Je nach gewünschter Semantik kann NullableDateTime als Struktur- oder Klassentyp implementiert werden.

Public Structure NullableDateTime
    Private m_Value As Date
    Private m_IsNull As Boolean

    Public Property Value() As Date
        Get
            Return m_Value
        End Get
        Set(ByVal Value As Date)
            m_Value = Value
        End Set
    End Property

    Public Property IsNull() As Boolean
        Get
            Return m_IsNull
        End Get
        Set(ByVal Value As Boolean)
            m_IsNull = Value
        End Set
    End Property
End Structure

In Visual Basic .NET besitzt der Datentyp DateTime die Parallelbezeichnung Date. Dementsprechend wäre es wünschenswert, in Visual Basic .NET den oben vorgestellten Datentyp NullableDateTime mit dem Namen NullableDate ansprechen zu können, um Konsistenz in der Benennung der Datentypen zu wahren. Die Imports-Direktive der Programmiersprache Visual Basic .NET erweist sich zu diesem Zweck als nützlich, erlaubt sie es doch, Namen von Namensräumen und Klassen Parallelbezeichnungen zuzuordnen. Unter der Annahme, daß der Datentyp NullableDateTime Mitglied des Namensraums MvpsOrg.DotNet.NullableTypes ist, reicht es aus, in den jeweiligen Codedateien vor der ersten Deklaration die Zeile Imports NullableDate = MvpsOrg.DotNet.NullableTypes.NullableDateTime aufzunehmen, um NullableDate direkt im Quellcode der Datei benutzen zu können. Nachstehendes Listing gibt ein Beispiel für die Verwendung des Datentyps NullableDate.

Imports NullableDate = MvpsOrg.DotNet.NullableTypes.NullableDateTime

Private m_Birthday As NullableDate

⋮

If m_Birthday.IsNull Then
    Me.LabelBirthday.Text = "(unbekannt)"
Else
    Me.LabelBirthday.Text = CStr(m_Birthday.Value)
End If

Nutzung des Datentyps SqlDateTime

Das .NET Framework stellt im Namensraum System.Data.SqlTypes den Strukturtyp SqlDateTime bereit. Dieser Datentyp ähnelt in seiner Funktionsweise der im vorigen Abschnitt vorgestellten Struktur NullableDateTime. Wie der Name des Typs andeutet, handelt es sich dabei nicht um einen universellen Datentyp zur Darstellung von Datumswerten, sondern um einen Datentyp, der bei der Arbeit mit Datenbanken, etwa in parametrisierten Anfragen, zum Einsatz gelangt. Seine Semantik hält sich eng an jene des Datentyps datetime aus dem Datenbankmanagementsystem SQL Server. Daher weist er einen wesentlich kleineren Wertebereich als der Datentyp DateTime auf. SqlDateTime ist aus diesem Grund als vollwertiger Ersatz für DateTime trotz der Fähigkeit des Umgangs mit Nullwerten nicht geeignet. Auf die Angabe eines Verwendungsbeispiels wird wegen der Ähnlichkeit zu NullableDateTime verzichtet.

Nullierbare Datentypen mittels der generischen Klasse Nullable(Of T)

Neu in .NET 2.0 ist die Unterstützung für Generizität. Generizität ermöglicht es, Klassen mit Typparametern auszustatten, in denen der Benutzer der Klasse den Typ von Methodenparametern, -rückgabewerten und Eigenschaften angeben kann. In der Klassenbibliothek des .NET Frameworks finden sich einige vordefinierte nützliche generische Klassen, darunter Nullable(Of T) im Namensraum System. Diese Klasse ermöglicht es, Datentypen um die Fähigkeit Nullwerte aufzunehmen, zu erweitern. Die wichtigsten Eigenschaften von Nullable(Of T) sind die Eigenschaft Value, deren Datentyp den im Typparameter T übergebenen Typ annimmt, sowie die Boolesche Eigenschaft HasValue, die mit der Eigenschaft IsNull von SqlDateTime vergleichbar ist und deren Wert ebenfalls Aufschluß darüber gibt, ob das Objekt einen gültigen Wert enthält. Nachstehendes Listing zeigt ein Beispiel für die Verwendung von Nullable(Of T) zur Darstellung nullierbarer Datumsangaben.

Private m_Birthday As Nullable(Of Date)

⋮

If m_Birthday.HasValue Then
    Me.LabelBirthday.Text = CStr(m_Birthday.Value)
Else
    Me.LabelBirthday.Text = "(unbekannt)"
End If

Zur Vereinfachung der Verwendung von Nullable(Of Date) bietet sich die Einführung einer Parallelbezeichnung an. Anschließend kann im Code komfortabel mit NullableDate auf den Datentyp Nullable(Of Date) verwiesen werden. Das Erben von Nullable(Of Date) zur Einführung eines neuen Typnamens ist nicht möglich, da es sich bei Nullable(Of Date) wie bei DateTime um einen Strukturtyp handelt. Da die Eigenschaft Value von Nullable(Of Date) den Datentyp DateTime besitzt, kann von den für DateTime definierten Operatoren Gebrauch gemacht werden.

Imports NullableDate = System.Nullable(Of Date)

Private m_Birthday As NullableDate

⋮

If m_Birthday.HasValue Then
    Me.LabelBirthday.Text = CStr(m_Birthday.Value)
Else
    Me.LabelBirthday.Text = "(unbekannt)"
End If

In Visual Basic 9.0 werden nullierbare Variablen direkt in der Syntax der Programmiersprache unterstützt. Anstelle von Nullable(Of T) kann T? geschrieben werden. Zudem stehen Operatoren für dreiwertige Logik mit nullierbaren Datentypen einschließlich Nullfortpflanzung zur Verfügung.

Private m_Birthday As Date?

⋮

If m_Birthday.HasValue Then
    Me.LabelBirthday.Text = CStr(m_Birthday.Value)
Else
    Me.LabelBirthday.Text = "(unbekannt)"
End If

Schlußwort

Erst ab .NET 2.0 ist mit der generischen Klasse Nullable(Of T) der Umgang mit Nullwerten bei Datumsangaben auf komfortable Art und Weise möglich, sodaß man nicht mehr auf einen selbst geschriebenen Ersatz oder gar das automatisierte Erstellen nullierbarer Datentypen mittels CodeDom zur Laufzeit angewiesen ist. Dennoch stellt Nullable(Of Date) weiterhin nicht in allen Fällen die beste Lösung dar. Bei der Arbeit mit Datenbanken wird es weiterhin von Vorteil sein, den Datentyp SqlDateTime zu benutzen. Es bleibt zu hoffen, daß die vielen verschiedenen Eigenbauten zur Simulation nullierbarer Datumsangaben nach und nach durch Nullable(Of Date) ersetzt werden und damit die Wiederverwendbarkeit von Code erhöht wird. C# 2.0 und Visual Basic 9.0 enthalten syntaktische Unterstützung für den Umgang mit nullierbaren Datentypen, andere .NET-Programmiersprachen werden folgen.