一、简介
众所周知,值类型变量不能null,这也是为什么它们被称为值类型。但是,在实际的开发过程中,也需要值为null的一些场景。例如以下场景:
场景1:您从数据库表中检索可空的整数数据列,数据库中的null值没有办法将此值分配给C#中Int32类型;
场景2:您在UI绑定属性,但是某些值类型的字段不是必须录入的(例如在人员管理中的死亡日期);
场景3:在Java中,java.Util.Date是一个引用类型,因此可以将此类型的字段设置为null。但是,在CLR中,System.DateTime是一个值类型,DateTime 变量不能null。如果使用Java编写的应用程序要将日期/时间传达给在CLR上运行的Web服务,如果Java应用程序发送是null, CLR中没有供对应的类型;
场景4:在函数中传递值类型时,如果参数的值无法提供并且不想传递,可以使用默认值。但有时默认值并不是最佳的选择,因为默认值实际也传递了一个默认的参数值,逻辑需要特殊的处理;
场景5:当从xml或json反序列化数据时,数据源中缺少某个值类型属性的值,这种情况很不方便处理。
当然,我们日常工作中还有很多类似的情况。
为了摆脱这些情况,Microsoft在CLR中增加了可为空值类型的概念。为了更清楚理解这一点,我们看一下System.Nullable
1 namespace System 2 { 3 [Serializable] 4 public struct Nullablewhere T : struct 5 { 6 private bool hasValue; 7 internal T value; 8 9 public Nullable(T value) {10 this.value = value; 11 this.hasValue = true;12 }13 14 public bool HasValue { 15 get {16 return hasValue; 17 } 18 }19 20 public T Value { 21 get {22 if (!HasValue) { 23 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue); 24 }25 return value; 26 }27 }28 29 public T GetValueOrDefault() { 30 return value;31 } 32 33 public T GetValueOrDefault(T defaultValue) {34 return HasValue ? value : defaultValue;35 } 36 37 public override bool Equals(object other) { 38 if (!HasValue) return other == null; 39 if (other == null) return false;40 return value.Equals(other); 41 }42 43 public override int GetHashCode() {44 return HasValue ? value.GetHashCode() : 0; 45 }46 47 public override string ToString() { 48 return HasValue ? value.ToString() : "";49 } 50 51 public static implicit operator Nullable (T value) {52 return new Nullable (value);53 } 54 55 public static explicit operator T(Nullable value) { 56 return value.Value; 57 }58 }59 }
从上面的定义可以总结如下几点:
Nullable
类型也是一个值类型; Nullable
类型包含一个Value属性用于表示基础值,还包括一个Boolean类型的HasValue属性用于表示该值是否为null ; Nullable
是一个轻量级的值类型。Nullable 类型的实例占用内存的大小等于一个值类型与一个Boolean类型占用内存大小之和; Nullable
的泛型参数T必须是值类型。您只能将Nullable 类型与值类型结合使用,您也可以使用用户定义的值类型。
二、语法和用法
使用Nullable
示例:
1 Nullablei = 1;2 Nullable j = null;3 Nullable > k; //这是一个错误语法,编译会报错。
CLR还提供了一种简写的方式。
1 int? i = 1;2 int? j = null;
可以通过 Value 属性来获取基础类型的值。如下所示,如果不为null,则将返回实际的值,否则将抛出InvalidOperationException异常;您可以在调用Value属性的时,需要检查是否为null。
1 Nullablei = 1; 2 Nullable j = null; 3 4 Console.WriteLine(i.HasValue); 5 //输出结果:True 6 7 Console.WriteLine(i.Value); 8 //输出结果:1 9 10 Console.WriteLine(j.HasValue);11 //输出结果:False12 13 Console.WriteLine(j.Value);14 //抛异常: System.InvalidOperationException
三、类型的转换和运算
C#还支持简单的语法来使用Nullable
1 // 从System.Int32隐式转换为Nullable2 int? i = 5; 3 4 // 从'null'隐式转换为Nullable 5 int? j = null; 6 7 // 从Nullable 到Int32的显式转换 8 int k = (int)i; 9 10 // 基础类型之间的转换11 Double? x = 5; // 从Int到Nullable 的隐式转换12 Double? y = j; // 从Nullable 隐式转换Nullable
对Nullable
一元运算符(++、--、 - 等),如果Nullable
类型值是null时,返回null; 二元运算符(+、-、*、/、%、^等)任何操作数是null,返回null;
对于==运算符,如果两个操作数都是null,则表达式计算结果为true,如果任何一个操作数是null,则表达式计算结果为false;如果两者都不为null,它照常比较。
对于关系运算符(>、<、>=、<=),如果任何一个操作数是null,则运算结果是false,如果操作数都不为null,则比较该值。
见下面的例子:
1 int? i = 5; 2 int? j = null; 3 4 // 一元运算符 5 i++; // i = 6 6 j = -j; // j = null 7 8 // 二元运算符 9 i = i + 3; // i = 9 10 j = j * 3; // j = null;11 12 // 等号运算符(==、!=)13 var r = i == null; //r = false14 r = j == null; //r = true15 r = i != j; //r = true16 17 // 比较运算符(<、>、<=、>=)18 r = i > j; //r = false19 20 i = null;21 r = i >= j; //r = false,注意,i=null、j=null,但是>=返回的结果是false
Nullable
1 // 如果雇员的年龄返回null(出生日期可能未输入),请设置值0. 2 int age = employee.Age ?? 0;3 4 // 在聚合函数中使用三元操作符。5 int?[] numbers = {};6 int total = numbers.Sum() ?? 0;
四、装箱与拆箱
我们已经知道了Nullable
CLR采用一个特殊的规则来处理Nullable
在拆箱处理时,与装箱处反。CLR会检查拆箱的对象是否为null,如果是直接创建一个新的实例 new Nullable
1 int? n = null; 2 object o = n; //不会进行装箱操作,直接返回null值 3 4 Console.WriteLine("o is null = {0}", object.ReferenceEquals(o, null)); 5 //输出结果:o is null = True 6 7 8 n = 5; 9 o = n; //o引用一个已装箱的Int3210 11 Console.WriteLine("o's type = {0}", o.GetType());12 //输出结果:o's type = System.Int3213 14 o = 5;15 16 //将Int32类型拆箱为Nullable类型17 int? a = (Int32?)o; // a = 5 18 //将Int32类型拆箱为Int32类型19 int b = (Int32)o; // b = 520 21 // 创建一个初始化为null22 o = null;23 // 将null变为Nullable 类型24 a = (Int32?)o; // a = null 25 b = (Int32)o; // 抛出异常:NullReferenceException
五、GetType()方法
当调用Nullable
1 int? i = 10;2 Console.WriteLine(i.GetType());3 //输出结果是:System.Int324 5 i = null;6 Console.WriteLine(i.GetType()); //NullReferenceException
原因分析:
这是因为调用GetType()方法时,已经将当前实例进行了装箱,根据上一部分装箱与拆箱的内容,这里实际上调用的是Int32类型的GetType()方法。
调用值类型的GetType()方法时,均会产生装箱,关于这一点大家可以自己去验证。
六、ToString()方法
当调用Nullable
1 int? i = 10;2 Console.WriteLine(i.ToString());3 //输出结果:104 5 i = null;6 Console.WriteLine(i.ToString() == string.Empty);7 //输出结果:True
七、System.Nullable帮助类
微软还提供一个同名System.Nullable的静态类,包括三个方法:
1 public static class Nullable 2 { 3 //返回指定的可空类型的基础类型参数。 4 public static Type GetUnderlyingType(Type nullableType); 5 6 //比较两个相对值 System.Nullable对象。 7 public static int Compare (T? n1, T? n2) where T : struct 8 9 //指示两个指定 System.Nullable 对象是否相等。10 public static bool Equals (T? n1, T? n2) where T : struct11 }
在这里面我们重点说明一下GetUnderlyingType(Type nullableType)方法,另外两个方法是用来比较值的,大家可以自己研究。
GetUnderlyingType(Type nullableType)方法是用来返回一个可为空类型的基础类型,如果 nullableType 参数不是一个封闭的Nullable
1 Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable))); 2 //输出结果:System.Int32 3 4 Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null); 5 //输出结果:True 6 7 Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null); 8 //输出结果:True 9 10 Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);11 //输出结果:True
八、语法糖
微软对Nullable
简写 | 编译后的语句 |
1 int? i = 5; 2 3 int? j = null; 4 5 var r = i != null; 6 7 var v = (int) i; 8 9 i++;10 11 i = i + 3;12 13 r = i != j;14 15 r = i >= j;16 17 var k = i + j;18 19 double? x = 5;20 21 double? y = j; |
1 int? i = new int?(5); 2 3 int? j = new int?(); 4 5 var r = i.HasValue; 6 7 var v = i.Value; 8 9 i = i.HasValue ? new int?(i.GetValueOrDefault() + 1) : new int?();10 11 i = i.HasValue ? new int?(i.GetValueOrDefault() + 3) : new int?();12 13 r = i.GetValueOrDefault() != j.GetValueOrDefault() || i.HasValue != j.HasValue;14 15 r = i.GetValueOrDefault() >= j.GetValueOrDefault() && i.HasValue & j.HasValue;16 17 int? k = i.HasValue & j.HasValue ? new int?(i.GetValueOrDefault() + j.GetValueOrDefault()) : new int?();18 19 double? x = new double?((double) 5);20 21 double? y = j.HasValue ? new double?((double) j.GetValueOrDefault()) : new double?(); |