通过C#在Boxing / Unboxing值类型上从CLR中提取......
On Boxing:如果可空实例不是 null ,则CLR将该值从可空实例中取出并将其装箱。换句话说, Nullable&lt;值<5> 的Int32&gt; 被装入 boxed-Int32 ,值为5。
取消装箱:取消装箱只是获取对盒装对象的未装箱部分的引用的行为。问题是盒装值类型不能简单地拆箱成该值类型的可空版本,因为盒装值中没有 boolean hasValue 字段。因此,当将值类型拆箱到可空版本时,CLR必须分配 Nullable&lt; T&gt; 对象,将 hasValue 字段初始化为 true ,并将值字段设置为相同的值盒装值类型。这会影响您的应用程序性能(拆箱时的内存分配)。
为什么CLR团队为Nullable类型经历了这么多麻烦?为什么它不是简单地装入Nullable&lt; Int32&gt;首先是什么?
答案 0 :(得分:23)
我记得这种行为是最后一刻的变化。在.NET 2.0的早期测试版中,Nullable<T>
是一种“正常”值类型。拳击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");
在前一种方法(框null
- &gt;盒装Nullable<T>
)中,你不会得到“test is not null”但是你得到“object is not null”这很奇怪。
此外,如果他们将可以为boxed-Nullable<T>
的可空值:
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<T>
对象,将hasValue字段初始化为true,并将value字段设置为盒装值类型中的相同值。这会影响您的应用程序性能(拆箱时的内存分配)。
事实并非如此。 CLR必须在堆栈上分配内存来保存变量,无论它是否可为空。为额外的布尔变量分配空间没有性能问题。
答案 1 :(得分:9)
我认为将空值包装到空引用是有意义的。有一个盒装价值说“我知道如果我有价值,我会是Int32
,但我不”对我来说似乎不直观。最好从“非值”(值为HasValue
的值为false)的值类型版本转换为“非值”(空引用)的引用类型版本。
我相信这一改变是基于社区的反馈,顺便说一句。
即使对于值类型,这也允许使用as
:
object mightBeADouble = GetMyValue();
double? unboxed = mightBeADouble as double?;
if (unboxed != null)
{
...
}
这与使用引用类型处理“不确定转换”的方式更加一致:
object mightBeADouble = GetMyValue();
if (mightBeADouble is double)
{
double unboxed = (double) mightBeADouble;
...
}
(它也可能表现更好,因为只有一次执行时间类型检查。)
答案 2 :(得分:5)
通过此行为获得的一件事是盒装版本实现了底层类型支持的所有接口。 (出于所有实际目的,目标是使Nullable<int>
看起来与int
相同。)对boxed-Nullable<int>
而不是boxed-int
进行限制会阻止此行为。
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>
的盒装版本获取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
布尔值的空间)
答案 3 :(得分:0)
我猜这基本上就是它的作用。给出的描述包括您的建议(即装入Nullable<T>
)。
额外的是它在装箱后设置hasValue
字段。
答案 4 :(得分:0)
我认为行为的原因源于Object.Equals的行为,最值得注意的是,如果第一个对象为null而第二个对象不是,则Object.Equals返回false而不是调用Equals方法在第二个对象上。
如果在第一个对象为空但第二个对象不为的情况下,Object.Equals会在第二个对象上调用Equals方法,则对象为null-null Nullable&lt; T&gt;。与null相比,可能返回True。就个人而言,我认为适当的补救措施是使Nullable&lt; T&gt;的HasValue属性成为可能。与null引用的概念无关。关于在堆上存储布尔标志所涉及的开销,可以为每种类型提供Nullable&lt; T&gt;。将有一个静态的盒装空版本,然后提供拆箱静态盒装空副本将产生一个空的Nullable&lt; T&gt;,并且取消装箱任何其他实例将产生一个填充的。