type
status
date
summary
tags
category
icon

一、背景

对于引用类型,C#语言已经提供了表示信息缺失的方法:null应用。但是对于值类型,C# 1中并没有相对应的表示空值的方法,于是在当时,较为普遍的做法是:
  • 当数据缺失时,采用预设值,例如decimal.MaxValue
  • 单独维护一个布尔型的标志来表示其他字段是实际值还是默认值。这样在访问字段前检查该标志,即可知道该字段当前值是否有效
上述两种方法都不太理想。第一种方式可能压缩了有效值的范围,而第二种方式可能导致很多冗余和逻辑重复。
鉴于上述这种情景,在C# 2中,可空值类型应运而生。可空值类型可以说封装了前面第二种方式:为每个值类型维护了一个额外的标志,用该标志来指示当前值是否可用。

二、可空值类型Nullable

2.1 Nullable<T>结构体

可空值类型特性背后的核心要素是Nullable<T>结构体。Nullable<T>的一个早期版本如下所示:
以上代码显示:该结构体声明了唯一的构造器,并将hasValue的初始值设为true,该结构体类型还隐含了一个无参构造器(结构体类型的共性)。无参构造器会将hasValue的初始值设为false,将value的初始值设为T类型的默认值
以下类型是非法的:
Nullable<string>
string类型是引用类型
Nullable<int[]>
数组是引用类型,与内部元素是否是值类型无关
Nullable<ValueType>
ValueType本身并不是值类型
Nullable<Enum>
Enum本身也不是值类型
Nullable<Nullable<int>>
Nullable<int>是可空类型本身
Nullable<Nullable<Nullable<int>>>
将可空类型嵌套也没有用
 
📕 另外,Nullable<T>结构体还提供了如下一些方法和运算符:
  • 无参数的GetValueOrDefault()方法负责返回结构体中的值,如果HasValue是false,则返回默认值
  • 带参数的GetValueOrDefault(T defaultValue) 方法同样负责返回结构体中的值,如果HasValue是false,则返回由实参指定的默认值
  • Nullable<T>重写了object类的Equals(object)GetHashCode()方法,使其行为更加明确:首先比较HasValue属性;当两个比较对象的HasValue均为true时,再比较Value属性是否相等
  • 可以执行从T到Nullable<T>的隐式类型转换。该转换总是会返回对应的可空值,并且HasValue为true。该隐式转换等同于调用带参数的构造器
  • 可以执行从Nullable<T>到T的显式类型转换。当HasValue为true时返回封装于其中的值,当HasValue为false时则抛出InvalidOperationException。该转换等同于使用Value属性

2.2 装箱行为

当涉及装箱行为时,可空值类型和非可空值类型的行为有所不同。
非可空值类型被装箱时,返回结果的类型就是原始的装箱类型,例如:
在C#中,“装箱int”和int之间的区别通常是不可见的:如果执行o.GetType(),返回的Type值会和typeof(int)的结果相同。
 
然而,可空值类型并没有直接对等的装箱类型。Nullable<T>类型的值进行装箱后的结果,视HasValue属性的值而定:
  • 如果HasValue为false,那么结果是一个null引用
  • 如果HasValue为true,那么结果是“装箱T”对象的引用
以上方法是理想的装箱行为,但是有一个奇怪的副作用:System.Object中声明的GetType()方法为非虚方法(不能重写),对某个值类型调用GetType()方法时总会先触发一次装箱操作。如果对可空值类型调用GetType(),要么会引发NullReferenceException,要么会返回对应的非可空值类型。

2.3 语言层面支持

? 后缀

Nullable<T>类型有一个简化版的写法,就是在类型名后添加?后缀。下面4个声明完全等价:
上面4中写法产生的IL代码没有任何区别。

null字面量

在C# 2中,null的含义得到了扩展:表示一个null引用,或者表示一个HasValue为false的可空类型的值。null引用和可空值类型不容易辨明,例如下面两行代码是等价的:

转换

前面讲过,存在从T到Nullable<T>的隐式类型转换,以及从Nullable<T>到T的显示类型转换。此外,C#语言还允许链式转换。对于任意两个非可空的值类型S和T,如果存在从S到T的类型转换,那么以下类型转换都是合法的:
  • Nullable<S>到Nullable<T>的类型转换
  • S到Nullable<T>的类型转换
  • Nullable<S>到T的显示类型转换

as运算符与可空值类型

在C# 2中,as运算符也可以用于可空值类型了。该运算符的返回值为一个可空类型的值:当原始引用的类型为null或目标类型不匹配时,返回null值。否则返回一个有意义的值,示例如下:
使用as运算符,只需一步操作就能把任意引用安全的转换成一个值。对于目标结果是Nullable<T>类型的表达式来说,as是很方便的运算符。
💡
对可空类型使用as运算符,性能出奇地底。大部分情况下,这不算太大的问题,但是依然比先用is运算符判断类型,然后进行强制类型转换性能低。

空合并运算符 ??

📕
在实际编码中,总会有使用可空值类型的需求:当一个表达式运算结果为null时,为变量提供一个默认值。C# 2引入了 ?? 运算符来解决上述问题,称为空合并运算符。
?? 是一个二元运算符,first ?? second 表达式的计算分为以下几个步骤:
  1. 计算first表达式;
  1. 若结果不为null,则整个表达式的结果等于first的计算结果;
  1. 若结果为空,则继续计算second表达式,整个表达式的结果为second的计算结果
👉 上述规则中有一个重点需要强调:如果第1个操作数的类型是可空值类型,同时第2个操作数是第1个操作数对应的非可空值类型,整个表达式的类型就是该非可空值类型,例如以下代码是合法的:
这样的赋值之所以合法,是因为b是非可空的,所以整个表达式的返回值将不可能为null。另外,??表达式还可以自由组合使用,例如x ? y ?? z,如果x为空就计算y;如果x和y都为空,就计算z。
C# 6引用了空值条件运算符?. ,该运算符便利了作为表达式结果的空值处理。在代码中把?.??运算符组合使用,可以发挥处理空值的强大作用。
Unity 踩坑 安卓打包 JNI FatalError called:Unity 笔记 贴图导入设置
  • Twikoo
  • Cusdis