C# 装箱/取消装箱可空类型-为什么使用此实现?
通过装箱/拆箱值类型上的C#从CLR提取 装箱:如果可空实例不是null,CLR将从可空实例中取出值并装箱。换句话说,值为5的NullableC# 装箱/取消装箱可空类型-为什么使用此实现?,c#,.net,clr,nullable,C#,.net,Clr,Nullable,通过装箱/拆箱值类型上的C#从CLR提取 装箱:如果可空实例不是null,CLR将从可空实例中取出值并装箱。换句话说,值为5的Nullable被装箱到值为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");
在前一种方法中(boxnull
->boxnull
),您不会得到“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);