我们知道,一个值类型的变量永远不可能为null.它总是包含值类型本身。遗憾的是,这在某些情况下会成为问题。例如,设计一个数据库时,可将一个列定义成为一个32位的整数,并映射到FCL的Int32数据类型。但是,数据库中的一个列可能允许值为空;用Microsoft .NET Framework处理数据库可能变得相当困难,因为在CLR中,没有办法将一个Int32值表示为null.
  Microsoft ADO.NET的表适配器确实支持可空类型。但遗憾的是,System.Data.SqlType命名空间中的值类型没有用可空类型替换,部分原因是类型之间没有"一对一"的对应关系。例如,SqlDecimal类型对最大允许38位数,而普通的Decimal类型最大只允许29位数。
  还有一个例子:在java中,java.util.Date类是一个引用类型,所以该类型的一个变量能设为null.但在CLR中,System.DateTime是一个值类型,一个DateTime变量永远都不能设为null.如果用java写的一个应用程序想和运行CLR的一个web服务交流日期/时间,那么一旦java发送了一个null,就会出问题,因为CLR不知道如何表示null,不知道如何操作它。
  为了解决这个问题,Microsoft在CLR中引入了可空值类型(nullable value type)的概念。为了理解它们是如何工作的,先看一看System.Nullable<T>类。他是在FCL中定义的。以下是System.Nullable<T>类型定义的逻辑表示:
  [Serializable]
  [DebuggerStepThrough]
  public struct Nullable<T> where T : struct
  {
  #region Sync with runtime code
  //下面两个字段表示状态
  internal T value;
  internal bool has_value;
  #endregion
  public Nullable(T value)
  {
  this.has_value = true;
  this.value = value;
  }
  public bool HasValue
  {
  get { return has_value; }
  }
  public T Value
  {
  get
  {
  if (!has_value)
  throw new InvalidOperationException("Nullable object must have a value.");
  return value;
  }
  }
  public override bool Equals(object other)
  {
  if (other == null)
  return has_value == false;
  if (!(other is Nullable<T>))
  return false;
  return Equals((Nullable<T>)other);
  }
  bool Equals(Nullable<T> other)
  {
  if (other.has_value != has_value)
  return false;
  if (has_value == false)
  return true;
  return other.value.Equals(value);
  }
  public override int GetHashCode()
  {
  if (!has_value)
  return 0;
  return value.GetHashCode();
  }
  public T GetValueOrDefault()
  {
  return value;
  }
  public T GetValueOrDefault(T defaultValue)
  {
  return has_value ? value : defaultValue;
  }
  public override string ToString()
  {
  if (has_value)
  return value.ToString();
  else
  return String.Empty;
  }
  public static implicit operator Nullable<T>(T value)
  {
  return new Nullable<T>(value);
  }
  public static explicit operator T(Nullable<T> value)
  {
  return value.Value;
  }
  //
  // These are called by the JIT
  //
  #pragma warning disable 169
  //
  // JIT implementation of box valuetype System.Nullable`1<T>
  //
  static object Box(T? o)
  {
  if (!o.has_value)
  return null;
  return o.value;
  }
  static T? Unbox(object o)
  {
  if (o == null)
  return null;
  return (T)o;
  }
  #pragma warning restore 169
  }
  可以看出,这个类封装了也可以为null的一个值类型的表示。由于Nullable<T>本身是一个值类型,所以它的实例仍然是"轻量级"的。也就是说,实例仍然在栈上,而且一个实例的大小就是原始值类型的大小加上一个Boolean字段的大小。注意,Nullable的类型参数T被约束为struct.这是由于引用类型的变量已经可以为null,所以没必要再去照顾它
  现在,如果想要在代码中使用一个可空的Int32,就可以向下面这样写:
  Nullable<Int32> x = 5;
  Nullable<Int32> y = null;
  Console.WriteLine("x: HasValue={0}, Value={1}",x.HasValue, x.Value);
  Console.WriteLine("y: HasValue={0}, Value={1}",y.HasValue, y.GetValueOrDefault());
  输出的结果为:
  x: HasValue=True, Value=5
  y: HasValue=False, Value=0
  一、C#对可空值类型的支持
  注意,C#允许在代码中使用简单的语法来初始化上述两个Nullable<Int32>变量x和y.事实上,C#开发团队希望将可空值类型集成到C#语言中,是它们成为"一等公民".为此,C#提供了一个更清晰的语法来处理可空值类型。C#允许用问号表示法来声明并初始化x和y变量:
  Int32? x =5;
  Int32? y =null;
  在C#中,Int32等价于Nullable<Int32>.但是,C#在此基础上更进一步,允许开发人员在可空实例上进行转换和转型。C#还允许开发人员向可空实例类型应用操作符。以下代码对此进行了演示;
  private static void ConversionsAndCasting()
  {
  // 从可空的 Int32 转换为 Nullable<Int32>
  Int32? a = 5;
  // 从'null'隐式转换为 Nullable<Int32>
  Int32? b = null;
  // 从 Nullable<Int32> 显示转换为 Int32
  Int32 c = (Int32)a;
  // 在可空基类型之间的转型
  Double? d = 5; // Int32 转型到 Double? (d是double类型 值为5)
  Double? e = b; // Int32? 转型到 Double? (e为 null)
  }
  C#还允许向可空实例应用操作符。下面是一些例子:
  private static void Operators()
  {
  Int32? a = 5;
  Int32? b = null;
  // 一元操作符 (+ ++ - -- ! ~)
  a++; // a = 6
  b = -b; // b = null
  // 二元操作符 (+ - * / % & | ^ 《 》)
  a = a + 3; // a = 9
  b = b * 3; // b = null;
  // 相等性操作符 (== !=)
  if (a == null) { /* no */ } else { /* yes */ }
  if (b == null) { /* yes */ } else { /* no */ }
  if (a != b) { /* yes */ } else { /* no */ }
  // 比较操作符 (<, >, <=, >=)
  if (a < b) { /* no */ } else { /* yes */ }
  }
  下面总结了C#如何解析操作符:
  * 一元操作符 操作符是null,结果也是null
  * 二元操作符 两个操作符中任何一个是null,结果就是null.但有一个例外,它发生在将&和|操作符应用于Boolean?操作数的时候。在这种情况下,这两个操作符的行为和SQL的三值逻辑一样的。对于这两个操作符,如果两个操作符都不是null,那么操作符和平常一样工作。如果两个操作符都是null,结果就是null.特殊情况就是其中之一为null时发生。下面列出了针对操作符的各种true,false和null组合:
  操作符→ true false null
  操作符↓
  true & = true
  | = true & = false
  | = true & = null
  | = true
  false & = false
  | = true
  & = false
  | = false & = false
  | = null
  null & = null
  | = true & = false
  | = null & = null
  | = null
  * 相等性操作 两个操作符都是null,两者相等。一个操作符为null,则两个不相等。两个操作数都不是null,就比较值来判断是否相等。
  * 关系操作符 两个操作符任何一个是null,结果就是false.两个操作数都不是null,就比较值。
  应该注意的是,操作符实例时会生成大量代码。例如以下方法:
  private static Int32? NullableCodeSize(Int32? a, Int32? b) {
  return (a + b);
  }
  在编译这个方法时,会生成相当多的IL代码,而且会使对可空类型的操作符慢于非可控类型执行的同样的操作。编译器生成的代码等价于以下C#代码:
  private static Nullable<Int32> NullableCodeSize(
  Nullable<Int32> a, Nullable<Int32> b) {
  Nullable<Int32> nullable1 = a;
  Nullable<Int32> nullable2 = b;
  if (!(nullable1.HasValue & nullable2.HasValue)){
  return new Nullable<Int32>();
  }
  return new Nullable<Int32>(nullable1.GetValueOrDefault() + nullable2.GetValueOrDefault());
  }
  19.2 C#的空结合操作符
  C#提供了一个所谓的"空结合操作符",即??操作符,它要获取两个操作符。假如左边的操作符不为null,就返回操作符这个操作符的值。如果左边的操作符为null,就返回右边的操作符的值。利用空接合操作符,可方便地设置的默认值。
  空接合操作符的一个妙处在于,它既能用于引用类型也能用于可空值类型。以下代码演示了如何使用??操作符:
  private static void NullCoalescingOperator() {
  Int32? b = null;
  // 下面这行等价于:
  // x = (b.HasValue) ? b.Value : 123
  Int32 x = b ?? 123;
  Console.WriteLine(x); // "123"
  // 下面这行等价于:
  // String temp = GetFilename();
  // filename = (temp != null) ? temp : "Untitled";
  String filename = GetFilename() ?? "Untitled";
  }
  有人争辩说??操作符不过是?:操作符的"语法糖"而已,所以C#团队不应该将这个操作符添加到语言中。实际上,??提供了重大的语法上的改进。
  第一个改进是??操作符能更好的支持表达式托福答案
  Func<String> f = () => SomeMethod ?? "Untitled";
  相比下一行代码,上述代码更容易容易阅读和理解。下面这行代码要求进行变量赋值,而且用一个语句还搞不定:
  Func<String> f = () => { var temp = SomeMethod(); return temp !=null ?temp : "Untitled"; }
  第二个改进就是??在符合情形下更好用。例如,下面这行代码:
  String s = SomeMethod() ?? SomeMethod2 ?? "Untitled";
  它比下面这一堆代码更容易理解和阅读:
  String s;
  var sm1 = SomeMethod();
  if (sm1 != null) s = sm1;
  else {
  var sm2 = SomeMethod2();
  if (sm2 !=null) s = sm2;
  else
  s = "Untitled";
  }
  三、CLR对可空值类型的特殊支持
  1.可空值类型的装箱
  先假定有一个为null的Nullable<Int32>变量。如果将该变量传给一个期待获取一个Object的方法,那么该变量必须装箱,并将对已装箱的Nullable<Int32>的一个引用传给方法。但是,这在逻辑上讲不通,因为现在向方法传递的一个非null的值--而Nullable<Int32>变量逻辑上包含的是null值。为了解决这个问题,CLR会在装箱一个可空变量时执行一些特殊代码。
  具体地说,当CLR对一个Nullable<T>实例进行装箱时,会检查它是否为null.如果是CLR不实际装箱任何内容,并返回null.如果可空类型实例不为null,CLR从可空实例中取出值,并对其进行装箱。也就是说,一个值为5的Nullable<Int32>会装箱成为值为5的一个已装箱的Int32.以下代码对这一行进行了演示:
  private static void Boxing() { www.yzyxedu.com
  // 对Nullable<T>进行装箱,要么返回null,要么返回一个已装箱的T
  Int32? n = null;
  Object o = n; // o 为 null
  Console.WriteLine("o is null={0}", o == null); // "True"
  n = 5;
  o = n; // o 引用一个已装箱的Int32
  Console.WriteLine("o's type={0}", o.GetType()); // "System.Int32"
  }
  其实在第一节中的Nullable<T>源码中已有显示,如:
  static object Box(T? o)
  {
  if (!o.has_value)
  return null;
  return o.value;
  }
  2. 可空值类型的拆箱
  CLR允许将一个已装箱的值类型T拆箱为一个T或者一个Nullable<T>.如果对已装箱值类型的引用是null,而且要把它拆箱为一个Nullable<T>,那么CLR会将Nullable<T>的值设为null.以下代码进行了演示:
  private static void Unboxing() {
  // 创建一个已装箱的Int32
  Object o = 5;
  // 把它拆箱为一个 Nullable<Int32> 和一个 Int32
  Int32? a = (Int32?)o; // a = 5
  Int32 b = (Int32)o; // b = 5
  // 创建初始化为null的一个引用
  o = null;
  // 把它"拆箱"为一个Nullable<Int32> 和一个 Int32
  a = (Int32?)o; // a = null
  b = (Int32) o; // NullReferenceException
  }
  同样的,在第一节中的Nullable<T>源码中已有显示,如:
  static T? Unbox(object o)
  {
  if (o == null)
  return null;
  return (T)o;
  }
  3.通过可空值类型调用GetType
  在一个Nullable<T>对象上调用GetType时,CLR实际上会"撒谎"说类型是T,而不是Nullable<T>.以下代码演示了这一行为:
  private static void GetTypeOfNullable() {
  Int32? x = 5;
  // 下面会显示"System.Int32"而不是"System.Nullable<Int32>"
  Console.WriteLine(x.GetType());
  }
  4. 通过可空值类型调用接口方法
  在下面代码中,将一个Nullable<Int32>类型的变量n转型为一个接口类型IComparable<Int32>.然而,Nullable<T>不像Int32那样实现了IComparable<Int32>接口。C#编译器允许这样的代码通过编译,而且CLR的校验器也会认为这样的代码是可验证的,从而允许我们使用一种更简洁的语法:
  Int32? n = 5;
  Int32 result = ((IComparable<Int32>)n)。CompareTo(5); // 能顺利通过编译和允许
  Console.WriteLine(result); // 0
  假如CLR没有提供这一特殊支持,那么为了在一个可空值类型上调用接口方法,就要写非常繁琐的代码。首先必须转型对已拆箱的值类型,然后才能转型为接口以发出调用:
  result = ((IComparable)(Int32)n)。CompareTo(5); // 这太繁琐了 www.yzyedu.com
  Console.WriteLine(result); // 0

CLR via C#可空值类型的更多相关文章

  1. CLR via C#(14)-可空值类型,关于?和??的故事

    我们都知道,值类型是不能为Null的,但是在实际应用中有些情形却需要将值类型置为null.因此,CLR中引用了可空值类型的用法.今天的文章中见到最多的符号估计就是?了吧. ?——初识可空值类型 1.  ...

  2. 【C#进阶系列】19 可空值类型

    可空值类型,正如字面意义上的,是可以为NULL的值类型. 这个东西存在的意义可以解决比如数据库的的Int可以为NUll的情况,使得处理数据库数据更简单. 实际上可空值类型就是Nullable<T ...

  3. [CLR via C#]19. 可空值类型

    我们知道,一个值类型的变量永远不可能为null.它总是包含值类型本身.遗憾的是,这在某些情况下会成为问题.例如,设计一个数据库时,可将一个列定义成为一个32位的整数,并映射到FCL的Int32数据类型 ...

  4. <NET CLR via c# 第4版>笔记 第19章 可空值类型

    System.Nullable<T> 是结构. 19.1 C# 对可空值类型的支持 C# 允许用问号表示法来声明可空值类型,如: Int32? x = 5; Int32? y = null ...

  5. [Clr via C#读书笔记]Cp19可空值类型

    Cp19可空值类型 主要解决的是和数据库中null对应的问题: System.Nullable结构:值类型: int?语法: 可空实例能够使用操作符: C#空合并操作符??; 即可用于引用类型,也可以 ...

  6. .NET 可空值类型

    Microsoft在CLR中引入了可空值类型(nullable value type)的概念. FCL中定义System.Nullable<T>类如下: [Serializable,Str ...

  7. .NET中可空值类型实现原理

    为了让.Net中的值类型可以赋值为null,微软特地添加了Nullable<T>类型,也可简写为T?.但是Nullable<T>自身是结构体,也是值类型,那么它是如何实现将nu ...

  8. 匹夫细说C#:可以为null的值类型,详解可空值类型

    首先祝大家中秋佳节快乐~ 0x00 前言 众所周知的一点是C#语言是一种强调类型的语言,而C#作为Unity3D中的游戏脚本主流语言,在我们的开发工作中能够驾驭好它的这个特点便十分重要.事实上,怎么强 ...

  9. Util应用程序框架公共操作类(十):可空值类型扩展

    当你使用可空的值类型时,你会发现取值很不方便,比如Guid? obj,你要从obj中获取值,可以使用Value属性obj. Value,但obj可能为null,这时候就会抛出一个异常. 可空值类型提供 ...

随机推荐

  1. 导出C++ dll文件

    方法1. 直接新建 Qt dll library, 使用工程自动创建的宏定义 方法2. (1)新建一个Empty的Win32项目(如ExampleDLL),选择Application type 为DL ...

  2. HDOJ(HDU) 2503 a/b + c/d(最大公约数问题)

    Problem Description 给你2个分数,求他们的和,并要求和为最简形式. Input 输入首先包含一个正整数T(T<=1000),表示有T组测试数据,然后是T行数据,每行包含四个正 ...

  3. HDOJ 2802 F(N)

    Problem Description Giving the N, can you tell me the answer of F(N)? Input Each test case contains ...

  4. 使用Vagrant machine

    使用Vagrant 查看Vagrant状态 vagrant status SSH vagrant ssh 共享文件 在vagrantfile中添加共享文件配置 Vagrant.configure(2) ...

  5. Bellman-Bord(贝尔曼-福特)

    include const int inf=0x3f3f3f3f; int main() { int m,n; scanf("%d%d",&n,&m); int u ...

  6. TCP内核源码分析笔记

    Table of Contents 1 术语 1.1 ABC 1.2 SACK 1.3 D-SACK 1.4 FACK 1.5 F-RTO 1.6 nagle算法 1.7 cork算法 1.8 tem ...

  7. Hadoop 的子项目

    Hadoop Common: 在0.20及以前的版本中,包含HDFS.MapReduce和其他项目公共内容,从0.21开始HDFS和MapReduce被分离为独立的子项目,其余内容为Hadoop Co ...

  8. Node.js连接数据库

    Node.js连接数据库前,须要安装对应的包.假设安装sql server 须要先装包node-sqlserver. 我们以mysql为案例来说明node.js查询mysql数据. 1.安装 node ...

  9. 谋哥:App自推广这个概念就由我来创立了!

    [谋哥每天一干货,第六十四篇] 昨天谋天团新增加了一名90后会员崔崔(微信号cuinianyyyy9),之前请教过我关于怎样从.Net到移动IOS开发然后创业的问题.我说你转到IOS,你须要自己学新语 ...

  10. [PWA] 5. Hijacking one type of request

    Previously we saw how to Hijacking all the reqest, but this is not useful. So now we want to see how ...