C#学习笔记(三)—–C#高级特性:可空值类型
2017-05-23 12:12
246 查看
C#高级特性:可空值类型
可空值类型
引用类型可以用一个空引用(null)来表示一个不存在的值,值类型,通常不能用来表示一个可空的值。例如:string s = null; // OK, Reference Type int i = null; // Compile Error, Value Type cannot be null
如果要表示一个可空的值类型,你必须用一种特殊的结构:可空值类型。它的表现形式为值类型后面加?标志。
int? i = null; // OK, Nullable Type Console.WriteLine (i == null); // True
Nullable<T>
结构
T?转换成System.Nullable<T>。
Nullable<T>是一个轻量且不可变的结构,只有两个字段:Value和HasValue。
System.Nullable<T>本质上是十分简单的:
public struct Nullable<T> where T : struct { public T Value {get;} public bool HasValue {get;} public T GetValueOrDefault(); public T GetValueOrDefault (T defaultValue); ... }
下面的代码:
int? i = null; Console.WriteLine (i == null);
可以翻译为:
Nullable<int> i = new Nullable<int>(); Console.WriteLine (! i.HasValue); // True
当HasValue为false的时候试图去检索value的值会抛出一个异常:InvalidOperationException。当HasValue为true时GetValueOrDefault()会返回Value的值。否则,它会返回一个new T()或者指定一个T类型的默认值。T?的默认值是null。
隐式和显式的可空类型转换
从T到T?的转换是隐式的,反之是显式的。int? x = 5; // implicit int y = (int)x; // explicit
显示转换与直接调用可空类型的Value属性是等价的,所以当HasVaue为false时,会抛出上述那个异常。
装箱和拆箱可空值
如果T?是装箱的,那么堆中的装箱值是T而不是T?。这种优化的结果存在的可能性是因为一个装了箱的值已经成为一个引用类型所以具备表达为null的能力。static void Main(string[] args) { int? a = 5; Console.WriteLine(a.GetType().Name);//int Console.WriteLine(typeof(int?)); //nullable<int> Console.ReadKey(); }
C#允许通过as运算符对一个可空值进行拆箱操作,如果操作失败,则返回一个null:
object o = "string"; int? x = o as int?; Console.WriteLine (x.HasValue); // False
运算符提升
Nullable<T>并没有定义诸如加减乘除大于小于等运算符。尽管如此,下面的代码仍然能正常工作:
int? x = 5; int? y = 10; bool b = x < y; // true
上例代码成功运行的原因是编译器会从实际值类型借去或提升小于运算符。在语义上,它会将上面最后那句表达式转换成下面的:
bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false;
换句话说,如果x和y的HasValue都返回true,那么会接着调用x和y的value属性接着运算,否则,会返回false。
运算符提升表示可以隐式的用T的运算符来对T?进行操作。你可以为T?自定义操作符来对一些可空行为进行支持。但是在大多数情况下,还是最好利用编译器来自动的应用系统的空值逻辑。下面的示例:
int? x = 5; int? y = null; // 相等运算符 Console.WriteLine (x == y); // False Console.WriteLine (x == null); // False Console.WriteLine (x == 5); // True Console.WriteLine (y == null); // True Console.WriteLine (y == 5); // False Console.WriteLine (y != 5); // True // 关系运算符 Console.WriteLine (x < 6); // True Console.WriteLine (y < 6); // False Console.WriteLine (y > 6); // False // 其他的一些 Console.WriteLine (x + 5); // 10 Console.WriteLine (x + y); // null (prints empty line)
①等于运算符(==和!=):已提升的等于运算符像操作引用类型一样操作可空值类型。这意味着两个null是相等的。
Console.WriteLine ( null == null); // True Console.WriteLine ((bool?)null == (bool?)null); // True
进而可以得出:
如果两个操作数中一个是null那么这两个操作数是不相等的;
如果两个操作数都不是null,那么他们将进行比较。
②关系运算符(<, <=, >=, >):空值的比较是无意义的,这表明操作数里如果有null,那么就总是返回false。
bool b = x < y; // Translation: bool b = (x.HasValue && y.HasValue) ? (x.Value < y.Value) : false; // b is false (assuming x is 5 and y is null)
③其他运算符符((+, −, *, /, %, &, |, ^, <<, >>, +, ++, –, !, ~):对于这些操作符来说,如果操作数里面有null,则会返回null。这对于SQL使用者来说是熟悉的。
int? c = x + y; // Translation: int? c = (x.HasValue && y.HasValue) ? (int?) (x.Value + y.Value) : null; // c is null (假设 x is 5 and y is null)
有一个例外是当在bool?上面使用&或|,我们稍后会讨论。
- ④混合使用可空和不可空运算符:可以混合使用可空和不可空的类型,这是因为不可空类型可以隐式的转换为可空类型:
int? a = null; int b = 2; int? c = a + b; // c is null - equivalent to a + (int?)b
⑤使用&和|计算bool?:如果操作数是一个bool?类型的,那么&和|操作符会将这个类型当做一个未知值来看待,所以,null|true返回true,因为:
如果未知值为假,那么结果为真
如果未知值为真,那么结果为真
同样,null|false为false。下面的例子枚举了其他的情况:
bool? n = null; bool? f = false; bool? t = true; Console.WriteLine (n | n); // (null) Console.WriteLine (n | f); // (null) Console.WriteLine (n | t); // True Console.WriteLine (n & n); // (null) Console.WriteLine (n & f); // False Console.WriteLine (n & t); // (null)
⑥空值合并运算符:??运算符是空值合并操作符,它可以在可空类型或引用类型中使用,它表示的意思是说,“如果结果不是一个空值,那么,把这个值给我,否则,给我设定一个默认值”:
int? x = null; int y = x ?? 5; // y is 5 int? a = null, b = 1, c = 2; Console.WriteLine (a ?? b ?? c); // 1 (first non-null value)
??运算符相当于在显示的有默认值的情况下调用了GetValueOrDefault方法。
可空值类型的应用场景
可空类型常用来表示未知值,在数据库编程的场景下十分常用。其中类可以通过可空的字段映射到数据表。如果这些字段是一些string类型的,这没有啥问题,但是,如果数据库中的其他表示值的字段映射到类中,那么可空值类型是有很大作用的。例如:// Maps to a Customer table in a database public class Customer { ... public decimal? AccountBalance; }
可空值类型也可以当作环境属性(ambient property是这么翻译的吗)的后备字段。
public class Row { ... Grid parent; Color? color; public Color Color { get { return color ?? parent.Color; } set { color = value == parent.Color ? (Color?)null : value; } } }
可空类型的替代选择
在可空值类型成为C#的一部分之前(C#2.0之前)有很多方法是可以处理可空值类型的。由于历史原因,这些方法的使用仍在运行中。其中的一个例子就是将一个非空值指定为空值,使用实例就是字符串和数组类。string.Indexof在找不到字符时会返回(魔数)-1:int i = "Pink".IndexOf ('b'); Console.WriteLine (i); // −1
然而,Array.IndexOf只有在索引小于0时才会返回-1.
// Create an array whose lower bound is 1 instead of 0: Array a = Array.CreateInstance (typeof (string), new int[] {2}, new int[] {1}); a.SetValue ("a", 1); a.SetValue ("b", 2); Console.WriteLine (Array.IndexOf (a, "c")); // 0
以下这些原因指明命名或定义一个“魔数”是存在问题的:
①使用它意味着每个值类型有不同的空值表示方式,而可空类型用一种统一的表示方法。
②由于上面的原因,可能会出现指定一个不合理的值
③魔数产生的错误可能在运行时才会表现出来
④类型不支持获得空值,类型可以传达程序的意图,允许编译器检查其正确性,实现编译器保证一致的规则集。
相关文章推荐
- C#学习笔记(三)—–C#高级特性:枚举类型和迭代
- C#学习笔记(三)—–C#高级特性:匿名类型
- C#学习笔记(三)—–C#高级特性中的委托与事件(中)
- C#学习笔记(三)—–C#高级特性中的委托与事件:关于事件
- C#学习笔记(三)—–C#高级特性:匿名方法
- C#学习笔记(三)—–C#高级特性:运算符重载
- 一个例子帮你搞懂C#语言高级特性系列(04) --- 匿名类型
- C#学习笔记(三)—–C#高级特性:try语句和异常
- C#学习笔记(三)—–C#高级特性:dynamic
- C#学习笔记(三)—–C#高级特性:扩展方法
- C#学习笔记(三)—–C#高级特性:实现迭代器的捷径
- C#学习笔记(三)—–C#高级特性:Lambda表达式
- C#学习笔记(三)—–C#高级特性中的委托与事件(上)
- C#2008与.NET 3.5 高级程序设计读书笔记(16)-- 类型反射、晚期绑定和基于特性的编程
- C# 3.0新特性初步研究 Part5:匿名类型_C#教程
- C# 3.0新特性系列:隐含类型var
- C# 3.0新特性系列:隐含类型var
- C# 3.0新特性(隐含类型var)
- 高效掌握C#笔记[第五章]C#的高级特性
- C# 3.0新特性系列:隐含类型var