什么是可空类型
C# 2.0 中引入了可空类型,可空类型是值类型,只是可空类型是包括null的值类型的。如:
DateTime? CreateTime= null;
上面代码DateTime?就是可空的DateTime类型,然而其实 "?"这个修饰符只是C#提供的一个语法糖,其实就是C# 2.0提供的可空类型是Nullable<T>,下面是可空类型的定义:
public struct Nullable<T> where T : struct)
其中T约束为值类型。所以上边DateTime的可空类型也可以如下定义
Nullable<DateTime> CreateTime=null
举个例子如下:
using System; namespace 可空类型Demo { class Program { static void Main(string[] args) { // 等同于DateTime? dt=DateTime.Now; Nullable<DateTime> dt = DateTime.Now; ShowInfo(dt); Console.WriteLine(); Console.WriteLine(); dt = new Nullable<DateTime>(); ShowInfo(dt); Console.ReadKey(); } private static void ShowInfo(DateTime? dt) { // HasValue 属性代表指示可空对象是否有值 // 在使用Value属性时必须先判断可空类型是否有值, // 如果可空类型对象的HasValue返回false时,将会引发InvalidOperationException异常 Console.WriteLine("可空类型是否有值:{0}", dt.HasValue); Console.WriteLine($"GetValueOrDefault(T)方法代表如果 HasValue 属性为 true,则为 Value 属性的值;否则为 defaultValue 参数值"); Console.WriteLine("GetValueorDefault():{0}", dt.GetValueOrDefault(DateTime.Now.AddDays(-1))); } } }
输出结果:
为什么使用可空类型
当我们在设计数据库的时候,我们可以设置数据库字段允许为null值,如果数据库字段是日期等这样在C#语言是值类型时,当我们把数据库表映射一个对象时,此时Datetime类型在C# 语言中是不能为null的,如果这样就会与数据库的设计有所冲突,这样开发人员就会有这样的需求了,同时微软也看出了用户有这样的需求,所以微软在C# 2.0中就新增加了一种类型——可空类型,即包含null值的值类型。
空合并操作符(?? 操作符)
??操作符是"空合并操作符",它代表的意思是两个操作数,如果左边的数不为null时,就返回左边的数,如果左边的数为null,就返回右边的数,这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型,之所以不能应用值类型(这里除了可空类型),因为??运算符要对左边的数与null进行比较,然而值类型,不能与null类型比较,所以就不支持??运算符,??这个运算符可以方便我们设置默认值,可以避免在代码中写if, else语句,简单代码数量,从而有利于阅读。
static void Main(string[] args) { int? numNull = null; int? numValue = 1; // ??和三目运算符的功能差不多的 // 所以下面代码等价于: // x=numNull.HasValue?b.Value:2; int x = numNull ?? 2; // 此时numValue不能null,所以y的值为nullhasvalue.Value,即输出1 int y = numValue ?? 3; Console.WriteLine("可空类型没有值的情况:{0}", x); Console.WriteLine("可空类型有值的情况:{0}", y); Console.ReadKey(); }
运行结果:
可空类型的装箱和拆箱
值类型存在装箱和拆箱的过程,可空类型也属于值类型,从而也有装箱和拆箱的过程的。装箱指的的从值类型到引用类型的过程,拆箱是从引用类型到值类型的过程。当把一个可空类型赋给一个引用类型变量时,此时CLR 会对可空类型(Nullable<T>)对象进行装箱处理,首先CLR会检测可空类型是否为null,如果为null,CLR则不进行实际的装箱操作(因为null可以直接赋给一个引用类型变量),如果不为null,CLR会从可空类型对象中获取值,并对该值进行装箱。当把一个已装箱的值类型赋给一个可空类型变量时,此时CLR会对已装箱的值类型进行拆箱处理,如果已装箱值类型的引用为null,此时CLR会把可空类型设为null。下面用一个示例来演示下可空类型的装箱和拆箱的使用:
// 可空类型装箱和拆箱的演示 private static void BoxedandUnboxed() { // 定义一个可空类型对象nullablewithoutvalue int? nullablewithoutvalue = null; // 装箱一个没有值的可空类型的对象 object obj = nullablewithoutvalue; Console.WriteLine("对null的可空类型装箱后obj 是否为null:{0}", obj == null); //拆箱成可空变量 Nullable<int> nullable = (int?)obj; Console.WriteLine("拆箱成可空变量是否为null:{0}", nullable == null); }
运行结果:
可空类型的装箱和拆箱操作大家可以就理解为非可空值类型的装箱和拆箱的过程,只是对于非可空类型因为包含null值,所以CLR会提前对它进行检查下它是否为空,为null就不不任何处理,如果不为null,就按照非可空值类型的装箱和拆箱的过程来装箱和拆箱。
小结
注意:当可空类型为null时,此时还是可以调用HasValue属性,即此时的返回值为false,可能就会有这样的疑问的,为什么对象为null了还可以调用属性,此时不会出现NullReferenceException异常吗?首先,可空类型是值类型,当可空类型为null时,此时可空类型并不是null(引用类型中的null),对于可空类型null这个是一个有效的值类型的,所以它调用HasValue不会抛出异常的。