C# 装箱/取消装箱可空类型-为什么使用此实现?

C# 装箱/取消装箱可空类型-为什么使用此实现?,c#,.net,clr,nullable,C#,.net,Clr,Nullable,通过装箱/拆箱值类型上的C#从CLR提取 装箱:如果可空实例不是null,CLR将从可空实例中取出值并装箱。换句话说,值为5的Nullable被装箱到值为5的boxed-Int32中 关于取消装箱:取消装箱只是获取对已装箱对象的未装箱部分的引用的行为。问题在于,装箱值类型不能简单地解除装箱到该值类型的可为空版本中,因为装箱值中没有布尔hasValue字段。因此,在将值类型取消装箱到可空版本时,CLR必须分配一个可空对象,将hasValue字段初始化为true,并将value字段设置为装箱值类型中

通过装箱/拆箱值类型上的C#从CLR提取

装箱:如果可空实例不是null,CLR将从可空实例中取出值并装箱。换句话说,值为5Nullable被装箱到值为5的boxed-Int32

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


为什么CLR团队在可空类型方面遇到这么多麻烦?为什么一开始它不被简单地装箱到一个可为空的中呢?

我猜它基本上就是这样做的。给出的描述包括您的建议(即装箱成一个
Nullable


额外的一点是,它在装箱后设置了
hasValue
字段。

我记得这种行为是在最后一分钟做出的改变。在.NET2.0的早期beta中,
Nullable
是一种“正常”值类型。装箱一个
null
int?
将其转换为带有布尔标志的装箱
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
->box
null
),您不会得到“test is not null”,但会得到“object is not null”,这很奇怪

此外,如果他们将一个可空值装箱为一个可空值,则:

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


澄清: 提供可空类型的全部目的是使处理没有意义值的变量变得容易。他们不想提供两种不同的、不相关的类型。
int?
的行为应该或多或少类似于简单的
int
。这就是C#提供提升操作符的原因

因此,在将值类型取消装箱到可空版本时,CLR必须分配一个
nullable
对象,将hasValue字段初始化为true,并将值字段设置为与装箱值类型中的值相同的值。这会影响应用程序性能(取消装箱期间的内存分配)


事实并非如此。CLR必须在堆栈上分配内存,以保存变量,无论它是否可为空。为额外的布尔变量分配空间并不存在性能问题。

我认为将空值框到空引用是有意义的。有一个装箱的值,上面写着“如果我有值,我知道我会成为
Int32
,但我没有”对我来说似乎是不直观的。最好从值类型版本的“not a value”(值
HasValue
为false)转到引用类型版本的“not a value”(空引用)

顺便说一句,我相信这个改变是根据社区的反馈做出的

这还允许将
作为
进行有趣的使用,即使对于值类型:

object mightBeADouble = GetMyValue();

double? unboxed = mightBeADouble as double?;
if (unboxed != null)
{
    ...
}
这与使用引用类型处理“不确定转换”的方式相比,更为一致:

object mightBeADouble = GetMyValue();

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

(它的性能也可能更好,因为只有一个执行时间类型检查。)

通过这种行为,您可以获得一个好处,即盒装版本实现了基础类型支持的所有接口。(目标是使
Nullable
在所有实际应用中都与
int
相同。)将装箱设置为
box-Nullable
而不是
box-int
将防止这种行为

另外,从可为null的
的装箱版本中获取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还是它的可空版本。(+您也获得了一些性能;节省了在装箱对象中存储
hasValue
布尔值的空间)

我认为这种行为的原因源于object的行为。Equals,最显著的事实是,如果第一个对象为null,而第二个对象为null,Equals返回false,而不是对第二个对象调用Equals方法


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

据我所知,在当前实现(步骤1)中,如果test为null,CLR不会将任何内容装箱并返回null。(步骤2)如果可为null的实例不为null,则将其装箱到一个boxed-int32中。步骤1不是解决了“obj不为空”的问题吗?他们为什么要做第二步?对不起,我好像遗漏了什么。普里茨:你的意思是他们会把
null
框到一个null引用,然后框
int?x=4
装箱-N
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();
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);