你的位置:首页 > ASP.net教程

[ASP.net教程]【备战面试之】四、可空类型NullableT到底是什么鬼

值类型为什么不可以为空

首先我们都知道引用类型默认值都是null,而值类型的默认值都有非null。

为什么引用类型可以为空?因为引用类型变量都是保存一个对象的地址引用(就像一个url对应一个页面),而引用类型值为null的时候是变量值指向了一个空引用(如同一个空的url)

那为什么值不能有空值呢?其实很简单,因为如int值范围是-2147483648到2147483647。其中根本就没有给null值留那么一个位置。

我们为什么需要用到可空类型

举个栗子吧,我们定义一个人(Person),它有三个属性出生日期(BeginTime)、死亡日期(EndTime)、年龄(Age)。

如果这个人还健在人世,请问怎么给死亡日期赋值?有人很聪明说“为空啊”。是的,这就是我们的需求。

微软在C#2.0的时候就为我们引入了可null值类型( System.Nullable<T> ),那么下面来定义Person类。

 1 public class Person 2 { 3   /// <summary> 4   /// 出生日期 5   /// </summary> 6   public DateTime BeginTime { get; set; } 7   /// <summary> 8   /// 死亡日期 9   /// </summary>10   public System.Nullable<DateTime> EndTiem { get; set; }11   public int Age12   {13     get14     {15       if (EndTiem.HasValue)//如果挂了(如果有值,证明死了)16       {17         return (EndTiem.Value - BeginTime).Days;18       }19       else//还没挂20       {21         return (DateTime.Now - BeginTime).Days;22       }23     }24   }25 }

 

这样,我们就可以很容易获得一个人的年龄了。

static void Main(string[] args){  Person p1 = new Person()  {    BeginTime = DateTime.Parse("1990-07-19")  };  Person p2 = new Person()  {    BeginTime = DateTime.Parse("1893-12-26"),    EndTiem = DateTime.Parse("1976-09-09")  };  Console.WriteLine("我今年" + p1.Age + "岁。");  Console.WriteLine("毛爷爷活了" + p2.Age + "岁。");  Console.ReadKey();}

可空类型的实现

我们前面用到了 System.Nullable<DateTime> 来表示可空时间类型,其实平时我们用得更多的是 DateTime? 直接在类型T后面加一个问号,这两种是等效的。多亏了微软的语法糖。

我们来看看 System.Nullable<T> 到底是何物。

搜噶,原来是一个结构。还看到了我们属性的 HasValue和Value属性。原来竟这般简单。一个结构两个属性,一个存值,一个存是否有值。那么下面我们也来试试吧。

不好意思,让大家失望了。前面我们就说过了,值类型是不可以赋值null的(结构也是值类型)。

怎么办!怎么办!不对啊,微软自己也是定义的结构,它怎么可以直接赋值null呢。(奇怪,奇怪,毕竟是人家微软自己搞得,可能得到了特殊的待遇吧)

可是,这样就让我们止步了吗?NO!我们都知道,看微软的IL(中间语言)的时候,就像脱了它的衣服一样,很多时候不明白的地方都可以看个究竟,下面我们就去脱衣服。

首先,我们用几种不同的方式给可空类型赋值。

static void Main(string[] args){  System.Nullable<int> number1 = null;  System.Nullable<int> number2 = new System.Nullable<int>();  System.Nullable<int> number3 = 23;  System.Nullable<int> number4 = new System.Nullable<int>(88);  Console.ReadKey();}  

 

然后用reflector看编译后的IL。

原来如此,可空类型的赋值直接等效于构造实例。赋null时其实就是调用空构造函数,有值时就就把值传入带参数的构造函数。(柳暗花明又一村。如此,我们是否可以接着上面截图中的 MyNullable<T> 继续模拟可空类型呢?且继续往下看。)

public struct MyNullable<T> where T : struct{  //错误  1  结构不能包含显式的无参数构造函数   //还好 bool默认值就是false,所以这里不显示为 this._hasValue = false也不会有影响  //public MyNullable()  //{  //  this._hasValue = false;  //}  public MyNullable(T value)//有参构造函数  {    this._hasValue = true;    this._value = value;  }  private bool _hasValue;  public bool HasValue//是否不为空  {    get { return _hasValue; }  }  private T _value;  public T Value//值  {    get    {      if (!this._hasValue)//如没有值,还访问就抛出异常      {        throw new Exception(" 可为空的对象必须具有一个值");      }      return _value;    }  }}

 

哟西,基本上已经模拟出了可空类型出来的。(但是我们还是不能直接赋值,只能通过构造函数的方式来使用自定义的可空类型)。

全部代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace 可空类型{  public class Person  {    /// <summary>    /// 出生日期    /// </summary>    public DateTime BeginTime { get; set; }    /// <summary>    /// 死亡日期    /// </summary>    public MyNullable<DateTime> EndTiem { get; set; } //这里改用MyNullable    /// <summary>    /// 年龄    /// </summary>    public double Age    {      get      {        if (EndTiem.HasValue)//如果挂了(如果有值,证明死了)        {          return (EndTiem.Value - BeginTime).Days / 365;        }        else//还没挂        {          return (DateTime.Now - BeginTime).Days / 365;        }      }    }  }  public struct MyNullable<T> where T : struct  {    //错误  1  结构不能包含显式的无参数构造函数     //还好 bool默认值就是false,所以这里不显示为 this._hasValue = false也不会有影响    //public MyNullable()    //{    //  this._hasValue = false;    //}    public MyNullable(T value)//有参构造函数    {      this._hasValue = true;      this._value = value;    }    private bool _hasValue;    public bool HasValue//是否不为空    {      get { return _hasValue; }    }    private T _value;    public T Value//值    {      get      {        if (!this._hasValue)//如没有值,还访问就抛出异常        {          throw new Exception(" 可为空的对象必须具有一个值");        }        return _value;      }    }  }  class Program  {    static void Main(string[] args)    {      Person p1 = new Person()      {        BeginTime = DateTime.Parse("1990-07-19")      };      Person p2 = new Person()      {        BeginTime = DateTime.Parse("1893-12-26"),        EndTiem = new MyNullable<DateTime>(DateTime.Parse("1976-09-09"))//这里使用MyNullable的有参构造函数      };      Console.WriteLine("我今年" + p1.Age + "岁。");      Console.WriteLine("毛爷爷活了" + p2.Age + "岁。");      Console.ReadKey();    }  }}

View Code

 

和系统的可空类型得出了相同的结果。

总结

  • 可空类型是结构(也就是值类型)
  • 所以可空类型的null值和引用类型的null是不一样的。(可空类型的并不是引用类型的null,而是用结构的另一种表示方式来表示null)

 

有同学问,怎么样才可以做到直接赋值呢?这个我也没有什么好的办法,或许需要编译器的支持。

以上内容都是胡说八道。希望能对您有那么一点点用处,感谢阅读。

(首发链接:http://www.cnblogs.com/zhaopei/p/5537759.html )