我们知道,一个值类型的变量永远不可能为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. Android *.db-journal

    config.xml <!-- The default journal mode to use use when Write-Ahead Logging is not active. Choic ...

  2. 阿里云ECS试用

    公司在推一个大项目,感觉阿里云挺好用的,自己搞了台小机器平时可以跑着玩,而且可以做个跳板机,平时学校里的收费网直接用跳板机就可以访问了,直接写个脚本在自己机器上跑一下: #!/usr/bin/expe ...

  3. Android实现OCR扫描识别数字图片之图片扫描识别

    [Android实例] Android实现OCR扫描识别数字图片之图片扫描识别 Android可以识别和扫描二维码,但是识别字符串呢? google提供了以下解决方案用的是原来HP的相关资料. 可以吧 ...

  4. lightoj 1198 最大权重匹配

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1198 #include <cstdio> #include <cst ...

  5. ubuntu12编译openwrt

    搭建编译环境 Ubuntu x64 12.04下的命令: sudo apt-get install subversion sudo apt-get install git sudo apt-get i ...

  6. 根据类名查所属jar包

    在开发时经常会参考一些现有的例子,但有些例子只有代码,代码中引用的类所属的jar包却没有明确说明,如何找到一个类所属的jar包,可以通过访问以下网址解决:http://www.findjar.com

  7. tomcat绿色版及安装版修改内存大小的方法

    1.对于安装版,比较方便了,直接运行tomcat6w.exe,选择Java选项卡, 在这里,可以设置初始化内存,最大内存,线程的内存大小. 初始化内存:如果机器的内存足够大,可以直接将初始化内存设置为 ...

  8. 多版本号并发控制(MVCC)在分布式系统中的应用

    QQ群:289150599 问题 近期项目中遇到了一个分布式系统的并发控制问题.该问题能够抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 ... Ln组成:D本质上是一个key-va ...

  9. Eclipse debug经常使用基本技巧

    1.F5单步调试,步入,进入函数体内部 2.F6单步调试.步过.不进入函数体 3.F7返回 4.F8运行到最后 5.退出时.右键点击右上角Debug选择退出就可以 $(function () { $( ...

  10. java 类型转换:

    数值数据类型: 1.自动类型转换 byte->short ->int->long-->float--->double 范转小的类型向范围大的类型号转换,由系统自动完成   ...