[你必须知道的.NET]第二十一回:认识全面的null
[2] [你必须知道的.NET]第二十一回:认识全面的null
[3] [你必须知道的.NET]第二十一回:认识全面的null
[4] [你必须知道的.NET]第二十一回:认识全面的null
[5] [你必须知道的.NET]第二十一回:认识全面的null
系列文章导航:
[你必须知道的.NET] 第四回:后来居上:class和struct
[你必须知道的.NET] 第五回:深入浅出关键字---把new说透
[你必须知道的.NET] 第六回:深入浅出关键字---base和this
[你必须知道的.NET] 第七回:品味类型---从通用类型系统开始
[你必须知道的.NET] 第八回:品味类型---值类型与引用类型(上)-内存有理
[你必须知道的.NET] 第九回:品味类型---值类型与引用类型(中)-规则无边
[你必须知道的.NET] 第十回:品味类型---值类型与引用类型(下)-应用征途
[你必须知道的.NET] 第十一回:参数之惑---传递的艺术(上)
[你必须知道的.NET] 第十二回:参数之惑---传递的艺术(下)
[你必须知道的.NET] 第十三回:从Hello, world开始认识IL
[你必须知道的.NET] 第十四回:认识IL代码---从开始到现在
[你必须知道的.NET] 第十六回:深入浅出关键字---using全接触
[你必须知道的.NET]第二十二回:字符串驻留(上)---带着问题思考
[你必须知道的.NET]第三十二回,深入.NET 4.0之,Tuple一二
2 Nullable<T>(可空类型)
一直以来,null都是引用类型的特有产物,对值类型进行null操作将在编译器抛出错误提示,例如:
//抛出编译时错误
int i = null;
if (i == null)
{
Console.WriteLine("i is null.");
}
正如示例中所示,很多情况下作为开发人员,我们更希望能够以统一的方式来处理,同时也希望能够解决实际业务需求中对于“值”也可以为“空”这一实际情况的映射。因此,自.NET 2.0以来,这一特权被新的System.Nullable<T>(即,可空值类型)的诞生而打破,解除上述诟病可以很容易以下面的方式被实现:
//Nullable<T>解决了这一问题
int? i = null;
if (i == null)
{
Console.WriteLine("i is null.");
}
你可能很奇怪上述示例中并没有任何Nullable的影子,实际上这是C#的一个语法糖,以下代码在本质上是完全等效的:
int? i = null;
Nullable<int> i = null;
显然,我们更中意以第一种简洁而优雅的方式来实现我们的代码,但是在本质上Nullable<T>和T?他们是一路货色。
可空类型的伟大意义在于,通过Nullable<T>类型,.NET为值类型添加“可空性”,例如Nullable<Boolean>的值就包括了true、false和null,而Nullable<Int32>则表示值即可以为整形也可以为null。同时,可空类型实现了统一的方式来处理值类型和引用类型的“空”值问题,例如值类型也可以享有在运行时以NullReferenceException异常来处理。
另外,可空类型是内置于CLR的,所以它并非c#的独门绝技,VB.NET中同样存在相同的概念。
Nullable的本质(IL)
那么我们如何来认识Nullable的本质呢?当你声明一个:
Nullable<Int32> count = new Nullable<Int32>();
时,到底发生了什么样的过程呢?我们首先来了解一下Nullable在.NET中的定义:
public struct Nullable<T> where T : struct
{
private bool hasValue;
internal T value;
public Nullable(T value);
public bool HasValue { get; }
public T Value { get; }
public T GetValueOrDefault();
public T GetValueOrDefault(T defaultValue);
public override bool Equals(object other);
public override int GetHashCode();
public override string ToString();
public static implicit operator T?(T value);
public static explicit operator T(T? value);
}
根据上述定义可知,Nullable本质上仍是一个struct为值类型,其实例对象仍然分配在线程栈上。其中的value属性封装了具体的值类型,Nullable<T>进行初始化时,将值类型赋给value,可以从其构造函数获知:
public Nullable(T value)
{
this.value = value;
this.hasValue = true;
}
同时Nullable<T>实现相应的Equals、ToString、GetHashCode方法,以及显式和隐式对原始值类型与可空类型的转换。因此,在本质上Nullable可以看着是预定义的struct类型,创建一个Nullable<T>类型的IL表示可以非常清晰的提供例证,例如创建一个值为int型可空类型过程,其IL可以表示为:
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<int32> a)
L_0000: nop
L_0001: ldloca.s a
L_0003: ldc.i4 0x3e8
L_0008: call instance void [mscorlib]System.Nullable`1<int32>::.ctor(!0)
L_000d: nop
L_000e: ret
}
对于可空类型,同样需要必要的小结:
- 可空类型表示值为null的值类型。
- 不允许使用嵌套的可空类型,例如Nullable<Nullable<T>> 。
- Nullable<T>和T?是等效的。
- 对可空类型执行GetType方法,将返回类型T,而不是Nullable<T>。
- c#允许在可空类型上执行转换和转型,例如:
int? a = 100;
Int32 b = (Int32)a;
a = null;
- 同时为了更好的将可空类型于原有的类型系统进行兼容,CLR提供了对可空类型装箱和拆箱的支持。