C# in depth ( 第四章 可空类型)
4.1没有值时怎么办
想为DateTime变量设为null,但编译器不允许 (一个商品还没有卖出,则没有购买日期)
4.1.1为什么值类型的变量不能为null
对于引用类型的变量来说,其值是一个引用,而值类型变量的值是它本身的真实数据。可以认为,一个非空引用值提供了访问一个对象的途径。然而,null相当于一个特殊的值,它意味着我不引用任何对象。
4.1.2 C#1.0中表示空值的模式
- 魔值 (DateTime.MinValue)
- 引用类型包装
- 额外的布尔标志
4.2 System.Nullable<T>和System.Nullable
静态类System.Nuallable提供了一些工具方法,可以简化可空类型的使用。
4.2.1Nullable<T>简介
- Nullable<Nullable<int>>是不允许的,即使Nullable<T>在其他方面符合值类型的一切特征。对于任何具体的可空类型来说,T的类型称为可空类型的基础类型(underlying type)。例如,Nullable<int>的基础类型就是int。
- Nullable<T> 的HasValue 和Value属性。 如果在一个Nullable的HasValue属性是false的前提下,读取其Value值会报InvalidOperationException。
- Nullable<T>有两个构造函数,默认构造函数创建“一个没有值的实例”。另一个构造函数则接受T的一个实例作为值。实例一经创建就是“不易变”的。
class NullableMembers { static void Display(Nullable<int> x) { Console.WriteLine("HasValue: {0}", x.HasValue); if (x.HasValue) { Console.WriteLine("Value: {0}", x.Value); Console.WriteLine("Explicit conversion: {0}", (int)x); } Console.WriteLine("GetValueOrDefault(): {0}", x.GetValueOrDefault()); Console.WriteLine("GetValueOrDefault(10): {0}", x.GetValueOrDefault(10));//如果这个nullable本身HasValue,就返回这个Value,如果没有Value就返回Default 此处为10 Console.WriteLine("ToString(): \"{0}\"", x.ToString()); Console.WriteLine("GetHashCode(): {0}", x.GetHashCode()); Console.WriteLine(); } static void Main() { Nullable<int> x = 5; x = new Nullable<int>(5); Console.WriteLine("Instance with value:"); Display(x); x = new Nullable<int>(); Console.WriteLine("Instance without value:"); Display(x); } }
- int 可以隐式的转换为 nullable<int>, nullable<int>可以显式的转换为int (如果nullable<int>没有值,则转换失败,报InvalidOpreationException。
- 覆盖GetHashCode,ToString和Equals 如果有值则返回该值的HashCode,没有则返回0,ToString也一样。
4.2.2 Nullable<T>装箱和拆箱
- Nullable<T>的实例要么装箱成空引用(如果没有值),要么装箱成T的一个已装箱的值(如果有值).它永远不会被装箱成“装箱的可空int",因为不存在这种类型。
已装箱的值可以拆箱成普通类型,或拆箱成对应的可空类型。(要么拆箱成T,要么拆箱成Nullable<T>。拆箱一个空引用时,如果拆箱成普通类型,会抛出一个NullReferenceException;但如果拆箱成恰当的可空类型,就会拆箱成没有值的一个实例
class BoxingAndUnboxing { static void Main() { Nullable<int> nullable = 5; object boxed = nullable; Console.WriteLine(boxed.GetType()); //System.Int32, 因为Nullable<int>是HasValue的,所以装箱是对int进行装箱,所以GetType返回的是Int32 int normal = (int)boxed; Console.WriteLine(normal);//5, 拆箱直接拆回int类型,然后会调用到int的toString()方法 nullable = (Nullable<int>)boxed; Console.WriteLine(nullable);//5, 拆箱返回Nullable<int>型,然后调用Nullable的Tostring()方法,因为HasValue是true,最后调用Int的ToString方法。 nullable = new Nullable<int>(); boxed = nullable; Console.WriteLine(boxed == null);//true,因为Nullable<int>的HasValue是False,所以会被装箱成null。 nullable = (Nullable<int>)boxed; Console.WriteLine(nullable.HasValue);//false,因为是直接拆箱成Nullable,即使boxed是null也不会报错,最后拆箱成一个HasValue为false的Nullable<int> } }
4.2.3 Nullable<T>实例的相等性
Nullable<T>覆盖了object.Equals(object),但没有引入任何相等性操作符,也没有提供Equals(Nullable<T>)方法
调用first.Equals(second)的具体规划如下。
- 如果first没有值,second为null,它们就是相等的
- 如果first没有值, second不为null,它们就是不相等的
- 如果first有值,second为null,它们就是不相等的;
- 否则,如果first的值等于second,它们就是相等的。
4.2.4来自非泛型nullable类的支持
4.3 C#2.0为可空类型提供的语法糖
4.3.1 ?修饰符
class SyntacticSugar { static void Main() { int? nullable = 5; object boxed = nullable; Console.WriteLine(boxed.GetType()); int normal = (int)boxed; Console.WriteLine(normal); nullable = (int?)boxed; Console.WriteLine(nullable); nullable = new int?(); boxed = nullable; Console.WriteLine(boxed == null); nullable = (int?)boxed; Console.WriteLine(nullable.HasValue); } }
4.3.2使用null进行赋值和比较
class AgeCalculation { class Person { DateTime birth; DateTime? death; string name; public TimeSpan Age { get { if (death == null) { return DateTime.Now - birth; } else { return death.Value - birth; } } } public Person(string name, DateTime birth, DateTime? death) { this.birth = birth; this.death = death; this.name = name; } } static void Main() { Person turing = new Person("Alan Turing", new DateTime(1912, 6, 23), new DateTime(1954, 6, 7)); Person knuth = new Person("Donald Knuth", new DateTime(1938, 1, 10), null); } }
4.3.3 可空转换和操作符
- 假如一个非可空的值类型支持一个操作符或者一种转换,而且那个操作符或者转换只涉及其他非可空的值类型时,那么可空的值类型也支持相同的操作符或转换,并且通常是将非可空的值类型转换成它们的可空等价物 比如int到long存在着一个隐式转换,就意味着int?到long?也存在着一个隐式的转换。
- 涉及可空类型的转换
- null到T?的隐式转换;
- T到T?的隐式转换;
- T?到T的显示转换。
4.3.4 可空逻辑 (P108 表4-2)
4.3.5 对可空类型使用as操作符
在C#2.0之前,as操作符只能用于引用类型。而在C#2.0中,它也可以用于可空类型。 其结果为可空类型的某个值或空值(如果原始引用为错误类型或空)或有意义的值。
static void PrintValueAsInt32(object o) { int? nullable = o as int?; Console.WriteLine(nullable.HasValue ? nullable.Value.ToString() : "null"); } PrintValueAsInt32(5); PrintValueAsInt32("some string");
使用is和强制转换要比使用as操作符快20倍!!!
4.3.6 空合并操作符
first ?? second
- 对first进行求值
- 如果结果非空,则该结果就是整个表达式的结果;
- 否则求second的值,其结果作为整个表达式的结果。
- 并非只能用于可空类型,还能应用于引用类型。
4.4 可空类型的新奇用法
4.4.1尝试一个不使用输出参数的操作
class NullableTryParse { static int? TryParse(string data) { int ret; if (int.TryParse(data, out ret)) { return ret; } else { return null; } } static void Main() { int? parsed = TryParse("Not valid"); if (parsed != null) { Console.WriteLine("Parsed to {0}", parsed.Value); } else { Console.WriteLine("Couldn't parse"); } } }
4.4.2 空合并操作符让比较不再痛苦