拳击/拆箱可空类型 – 为什么这个实施?

通过C#在Boxing / Unboxing值类型上从CLR中提取…

在Boxing上:如果可空实例不为null ,则CLR将该值从可空实例中取出并将其装箱。 换句话说,值为5Nullable 被装入一个值为5boxed-Int32

在取消装箱:取消装箱只是获取对盒装对象的未装箱部分的引用的行为。 问题是盒装值类型不能简单地拆箱成该值类型的可空版本,因为盒装值中没有boolean hasValue字段。 因此,当将值类型拆箱为可空版本时,CLR必须分配Nullable 对象,将hasValue字段初始化为true ,并将value字段设置为盒装值类型中的相同值。 这会影响您的应用程序性能(拆箱期间的内存分配)。

为什么CLR团队为Nullable类型经历了这么多麻烦? 为什么不首先将它装入Nullable ?

我记得这种行为是最后一刻的变化。 在.NET 2.0的早期测试版中, Nullable是一种“正常”值类型。 拳击nullint? 把它变成了盒装的int? 带有布尔标志。 我认为他们决定选择当前方法的原因是一致性。 说:

 int? test = null; object obj = test; if (test != null) Console.WriteLine("test is not null"); if (obj != null) Console.WriteLine("obj is not null"); 

在前一种方法(box null – > boxed Nullable )中,你不会得到“test is not null”但你得到“object is not null”这很奇怪。

另外,如果他们将一个可以为空的值boxed-Nullable

 int? val = 42; object obj = val; if (obj != null) { // Our object is not null, so intuitively it's an `int` value: int x = (int)obj; // ...but this would have failed. } 

除此之外,我相信当前的行为对于可空数据库值这样的场景非常有意义(想想SQL-CLR …)


澄清:

提供可空类型的重点是使处理没有意义值的变量变得容易。 他们不想提供两种截然不同的类型。 一个int? 应该表现得或多或少像一个简单的int 。 这就是为什么C#提供了提升的运营商。

因此,当将值类型拆箱为可空版本时,CLR必须分配Nullable对象,将hasValue字段初始化为true,并将value字段设置为盒装值类型中的相同值。 这会影响您的应用程序性能(拆箱期间的内存分配)。

这不是真的。 CLR必须在堆栈上分配内存以保存变量,无论它是否可为空。 为额外的布尔变量分配空间没有性能问题。

我认为将空值设置为空引用是有意义的。 有一个盒装价值说“我知道如果我有价值,我会是一个Int32 ,但我不是”对我来说似乎不直观。 最好从“非值”的值类型版本( HasValue为false的值)转换为“非值”的引用类型版本(空引用)。

我相信这一变化是基于社区的反馈,顺便说一句。

这也允许对值类型进行有趣的使用:

 object mightBeADouble = GetMyValue(); double? unboxed = mightBeADouble as double?; if (unboxed != null) { ... } 

这与使用引用类型处理“不确定转换”的方式更加一致,而不是之前的:

 object mightBeADouble = GetMyValue(); if (mightBeADouble is double) { double unboxed = (double) mightBeADouble; ... } 

(它也可能表现更好,因为只有一次执行时间类型检查。)

通过此行为获得的一件事是盒装版本实现了底层类型支持的所有接口。 (目的是使Nullable出于所有实际目的而与int相同。)对boxed-Nullable而不是boxed-int进行boxed-int会阻止此行为。

从MSDN页面,

 double? d = 44.4; object iBoxed = d; // Access IConvertible interface implemented by double. IConvertible ic = (IConvertible)iBoxed; int i = ic.ToInt32(null); string str = ic.ToString(); 

Nullable的盒装版本获取int也很简单 – 通常你不能取消装入原始src类型以外的类型。

 float f = 1.5f; object boxed_float = f; int int_value = (int) boxed_float; // will blow up. Cannot unbox a float to an int, you *must* unbox to a float first. float? nullableFloat = 1.4f; boxed_float = nullableFloat; float fValue = (float) boxed_float; // can unbox a float? to a float Console.WriteLine(fValue); 

在这里,您不必知道原始版本是int还是Nullable版本。 (+你也得到一些性能;在盒装对象中保存存储hasValue布尔值的空间)

我想这基本上就是它的作用。 给出的描述包括你的建议(即装入Nullable )。

额外的是它在装箱后设置hasValue字段。

我认为行为的原因源于Object.Equals的行为,最值得注意的是,如果第一个对象为null而第二个对象不是,则Object.Equals返回false而不是在第二个上调用Equals方法宾语。

如果在第一个对象为空但第二个对象不为的情况下,Object.Equals会在第二个对象上调用Equals方法,那么与null相比,null值Nullable 的对象可能返回True。 就个人而言,我认为正确的补救方法是使Nullable 的HasValue属性与null引用的概念无关。 关于在堆上存储布尔标志所涉及的开销,可以为每个类型Nullable 提供一个静态的盒装空版本,然后提供拆箱的静态盒装空副本将产生一个清空Nullable ,并取消装箱任何其他实例将产生一个填充的实例。