Nun, ich weiß nicht, ob die SqlTypes
Fall, aber es gibt definitiv einige technische Gründe, warum eine implizite Umwandlung zwischen DBNull.Value
und Werte von Nullable<T>
avec HasValue = false
würde nicht funktionieren.
Erinnern Sie sich, DBNull
ein Referenztyp ist, und trotz der Tatsache, dass Nullable<T>
handelt wie ein Referenztyp - indem er vorgibt, die Funktion eines null
Wert - es ist eigentlich ein Werttyp mit Wert-Semantik.
Insbesondere gibt es einen seltsamen Randfall, wenn Werte vom Typ Nullable<T>
sind eingepackt. Das Verhalten ist in der Laufzeit speziell auf Werte vom Typ Nullable<T>
zu einer Boxversion von T
und nicht eine Boxversion von Nullable<T>
.
Als die MSDN-Dokumentation erklärt es:
Wenn ein nullable Typ geboxt wird, boxt die Common Language Runtime automatisch den zugrunde liegenden Wert des Nullable(Of T)-Objekts, nicht das Nullable(Of T)-Objekt selbst. Das heißt, wenn die HasValue-Eigenschaft wahr ist, wird der Inhalt der Value-Eigenschaft eingegrenzt. Wenn der zugrundeliegende Wert eines nullable-Typs aus der Box entfernt wird, erstellt die Common Language Runtime eine neue Nullable(Of T)-Struktur, die mit dem zugrundeliegenden Wert initialisiert wird.
Wenn die HasValue-Eigenschaft eines nullbaren Typs false ist, ist das Ergebnis einer Boxing-Operation Nothing. Wird also ein gepackter nullable-Typ an eine Methode übergeben, die ein Objektargument erwartet, muss diese Methode darauf vorbereitet sein, den Fall zu behandeln, dass das Argument Nothing ist. Wenn Nothing in einen nullable-Typ unboxed wird, erstellt die Common Language Runtime eine neue Nullable(Of T)-Struktur und initialisiert deren HasValue-Eigenschaft auf false.
Jetzt stoßen wir auf ein kniffliges Problem: Die C#-Sprachenspezifikation (§4.3.2) besagt, dass wir keine Unboxing-Konvertierung verwenden können, um die DBNull.Value
in Nullable<T>
:
Für eine Unboxing-Konvertierung zu einer bestimmten nullbar-Typ zur Laufzeit erfolgreich zu sein, muss der Wert des Quelloperanden entweder null oder ein Verweis auf einen verschlüsselten Wert des zugrunde liegenden Nicht-nullbarer-Wert-Typ der nullbar-Typ . Wenn der Quelloperand ein Verweis auf ein inkompatibles Objekt ist, wird ein System.InvalidCastException
geworfen wird.
Und wir können keine benutzerdefinierte Konvertierung verwenden, um von object
à Nullable<T>
auch nicht gemäß § 10.10.3:
Es ist nicht möglich, eine vordefinierte Konvertierung direkt neu zu definieren. Daher dürfen Konvertierungsoperatoren nicht von oder nach object
weil es bereits implizite und explizite Konvertierungen zwischen object
und alle anderen Typen.
GUT, Sie oder ich konnte es nicht, aber Microsoft konnte die Spezifikation einfach ändern und es legal machen, oder? Das glaube ich nicht.
Warum? Nun, stellen Sie sich den beabsichtigten Anwendungsfall vor: Sie haben eine Methode, die Folgendes zurückgeben soll object
. In der Praxis gibt sie entweder DBNull.Value
ou int
. Aber wie kann der Compiler das wissen? Alles, was er weiß, ist, dass die Methode so spezifiziert ist, dass sie Folgendes zurückgibt object
. Und der anzuwendende Konvertierungsoperator muss zur Kompilierzeit ausgewählt werden.
OK, nehmen wir also an, es gibt einen magischen Operator, der von object
à Nullable<T>
und der Compiler kann auf irgendeine Weise erkennen, wann sie anwendbar ist. (Wir wollen nicht, dass es für jede Methode, die angegeben wird, um Folgendes zurückzugeben object
-- was soll sie tun, wenn die Methode tatsächlich einen string
?) Aber wir haben immer noch ein Problem: Die Umwandlung könnte zweideutig sein! Wenn die Methode entweder long
, oder DBNull.Value
und wir tun es int? v = Method();
Was ist zu tun, wenn die Methode eine boxed long
?
Damit dies wie vorgesehen funktioniert, müssen Sie das Äquivalent von dynamic
um den Typ zur Laufzeit zu bestimmen und auf der Grundlage des Laufzeittyps zu konvertieren. Damit haben wir aber eine weitere Regel gebrochen: Da die eigentliche Konvertierung erst zur Laufzeit ausgewählt wird, gibt es keine Garantie, dass sie tatsächlich erfolgreich ist. Aber implizit Konvertierungen sollen keine Ausnahmen auslösen.
An diesem Punkt bedeutet dies nicht nur eine Änderung des spezifizierten Verhaltens der Sprache, sondern auch einen potenziell erheblichen Leistungsabfall, und obendrein könnte es eine unerwartete Ausnahme auslösen! Das scheint ein ziemlich guter Grund zu sein, es nicht zu implementieren. Aber wenn Sie noch einen weiteren Grund brauchen, denken Sie daran, dass jedes Feature am Anfang minus 100 Punkte .
Kurz gesagt: Was Sie hier wirklich wollen, kann mit einer impliziten Konvertierung sowieso nicht erreicht werden. Wenn Sie das Verhalten von dynamic
verwenden Sie einfach dynamic
! Dies tut, was Sie wollen, und ist bereits in C# 4.0 implementiert:
object x = 23;
object y = null;
dynamic dx = x;
dynamic dy = y;
int? nx = (int?) dx;
int? ny = (int?) dy;
Console.WriteLine("nx.HasValue = {0}; nx.Value = {1}; ny.HasValue = {2};",
nx.HasValue, nx.Value, ny.HasValue);