是否有技术原因没有从 DBNull 隐式转换为可为空的类型

本文关键字:转换 类型 DBNull 技术 是否 | 更新日期: 2023-09-27 18:19:48

是否有技术原因导致没有从DBNull到各种可为空和/或SQL类型的隐式转换?我理解为什么转换当前没有发生,但不明白为什么当时没有创建隐式转换或添加到框架的后续版本中。

需要明确的是,我正在寻找技术原因,而不是"因为他们就是这样做的"或"我喜欢这样"。

是否有技术原因没有从 DBNull 隐式转换为可为空的类型

最直接的解释来自Microsoft的文档:

CLR 可为空类型不用于存储数据库空值,因为 ANSI SQL 空值的行为方式与空引用(或 Visual Basic 中的"无内容"(不同。

好吧,我不知道SqlTypes的情况,但肯定有一些技术原因,为什么在DBNull.ValueHasValue = false Nullable<T>值之间添加隐式转换是行不通的。

请记住,DBNull是一个引用类型,尽管Nullable<T>的行为类似于引用类型 - 通过假装能够接受null值 - 它实际上是一个值类型,具有值语义。

特别是,当 Nullable<T> 类型的值被装箱时,有一个奇怪的边缘情况。 该行为在运行时是特殊情况,将类型为 Nullable<T> 的值装箱到T的盒装版本,而不是装箱版本的Nullable<T>

正如 MSDN 文档所解释的那样:

将可为 null 的类型装箱时,公共语言运行库会自动装箱 Nullable(Of T( 对象的基础值,而不是 Nullable(Of T( 对象本身。也就是说,如果 HasValue 属性为 true,则 Value 属性的内容将装箱。当可为空类型的基础值取消装箱时,公共语言运行库将创建一个初始化为基础值的新 Nullable (Of T( 结构。

如果可为空类型的 HasValue 属性为 false,则装箱操作的结果为 Nothing。因此,如果将盒装可为空的类型传递给需要对象参数的方法,则必须准备该方法来处理参数为 Nothing 的情况。当 Nothing 被取消装箱为可为 null 的类型时,公共语言运行库将创建一个新的 Nullable(Of T( 结构,并将其 HasValue 属性初始化为 false。

现在我们遇到了一个棘手的问题:C# 语言规范 (§4.3.2( 说我们不能使用拆箱转换将DBNull.Value转换为Nullable<T>

若要在运行时成功将拆箱转换为给定的可为 null 的类型,源操作数的值必须为 null,或者是对可为 null 类型的基础不可为空值类型的装箱值的引用。如果源操作数是对不兼容对象的引用,则会引发System.InvalidCastException

而且,根据 §10.10.3,我们也不能使用用户定义的转换从 object 转换为 Nullable<T>

无法

直接重新定义预定义的转换。 因此,不允许转换运算符从object转换或转换为因为object和所有其他类型之间已经存在隐式和显式转换。

好吧,你或我做不到,但Microsoft可以修改规范,使其合法,对吧? 我不这么认为。

为什么? 好吧,想象一下预期的用例:您有一些指定返回object的方法。 实际上,它要么返回 DBNull.Value,要么返回 int 。 但是编译器怎么会知道呢? 它只知道该方法被指定为返回 object 。 并且必须在编译时选择要应用的转换运算符。

好的,所以假设有一些神奇的运算符可以从object转换为Nullable<T>,并且编译器有某种方式知道它何时适用。 (我们不希望它用于指定返回object的每个方法 - 如果该方法实际返回string,它应该怎么做? 但我们仍然有一个问题:转换可能是模棱两可的! 如果该方法返回longDBNull.Value,并且我们执行int? v = Method();,那么当该方法返回盒装long时,我们应该怎么做?

基本上,要按预期工作,您必须使用等效的 dynamic 在运行时确定类型并根据运行时类型进行转换。 但是,我们打破了另一条规则:由于实际转换仅在运行时选择,因此无法保证它实际上会成功。 但隐式转换不应引发异常。

因此,在这一点上,它不仅是对语言指定行为的更改,而且可能会对性能造成重大影响,最重要的是,它可能会引发意外异常! 这似乎是不实施它的一个很好的理由。 但是,如果您还需要一个原因,请记住,每个功能开始时都是负 100 分。

简而言之:无论如何,您在这里真正想要的东西都无法通过隐式转换来完成。 如果你想要dynamic的行为,只需使用dynamic! 这执行了您想要的操作,并且已在 C# 4.0 中实现:

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);