001-認識全面的null

021-認識全面的null

null
nullable
??運算符
null object

從什麼是null開始?

  1. null,一個值得尊敬的數據標識。

  2. 一般來說,null表示空類型,也就是表示什麼都沒有,但是“什麼都沒有”並不意味着“什麼都不是”。實際上,null是如此重要,以至於在JavaScript中,Null類型就作爲5中基本的原始類型之一,與Undefined、Boolean、Number和String並駕齊驅。但是null並不等同於0,"",string.Empty這些通常意義上的“零”值概念。相反,null具有實實在在的意義,這個意義就是用於標識變量引用的一種狀態,這種狀態表示沒有引用任何對象實例,也就是表示“什麼都沒有”,即不是Object實例,也不是User實例,而是一個空引用而已。

  3. 在.NET中,null表示一個對象引用是無效的。作爲引用類型變量的默認值,null是針對指針(引用)而言的,他是引用類型變量的專屬概念,表示一個引用類型變量聲明但未初始化的狀態。例子如下i。此時obj僅僅是一個保存在線程棧上的引用指針,不代表任何意義,obj未指向任何有效實例,而被默認初始化爲null。
    例如:object obj = null;

  4. object obj 和 object obj = null 的區別?
    主要體現在編譯器的檢查上,默認情況下,創建一個引用類型變量時,CLR即將其初始化爲null,表示不指向任何有效實例,所以本質上二者表示了相同的意義,但是有所區別。
    在這裏插入圖片描述

  5. 在.NET中,對null有如下的基本規則和應用:
    a. null爲引用類型變量的默認值,爲引用類型的概念範疇。
    b. null不等於0,"",string.Empty
    c. 引用is或as模式對類型進行判斷或轉換時,需要做進一步的null判斷
    d. 判斷一個變量是否爲null,可以應用 == 或 != 操作符來完成。
    e. 對任何值爲null的變量操作,都會拋出NullReferenceException異常。

Nullable (可空類型)

一直以來,null都是引用類型的特有產物,對值類型進行null操作將在編譯器拋出錯誤提示。例如

//拋出編譯時錯誤
int i = null;
if(i == null)
{
	Console.Write("i is null.");
}

正如上例中,很多情況下作爲開發人員,我們更希望能夠以統一的方法來處理,同時也希望解決實例業務需求中對“值”也可以爲“空”這一實際情況的映射,因此,自.NET2.0以來,這一特權被新的System.Nullable(即:可空值類型)的誕生而打破,可以用下述方法被實現:

  //Nullable<T>解決了這一問題
  int? i = null;
  if(i == null)
  {
        Console.Write("i is null.");
  }

一定很好奇爲什麼上述代碼中沒有Nullable,因爲這是C#的一個語法糖,以下代碼是完全等效的。顯然,我們更中意以第一種簡潔而優雅的方式來實現我們的代碼,但是在本質上Nullable和 T?他們是一路貨色。

int? i = null;
Nullable<int>  i = null;

可空類型的偉大意義在於,通過Nullable類型,.NET爲值類型添加“可空性”,例如Nullable的值就包含true ,false 和 null, 而Nullable則表示值既可以爲整形也可以爲null,同時,可空類型實現了統一的方式來處理值類型和引用類型的“空”值問題,例如值類型也可以享有在運行時以NullReferenceException異常來處理。
另外,可空類型是內置於CLR的,所以它並非C#的獨門絕技,VB.NET中同樣存在相同概念。

Nullable的本質(IL)

Nullable本質上仍是一個struct值類型,其 實例對象仍然分配在線程棧上。其中value屬性封裝了具體的值類型,Nullable進行初始化時,將值類型賦給value,可以從其構造函數獲悉(public Nullable(T value) { this.value = value; this.hasValue = true; } )。以下時Nullable在.NET中的定義:

public struct Nullable<T> where T: struct
{
    private bool hasValue;
    internal Tvalue;
    public Nullable(T value);
    public bool HasValue{get; }
    public T value { get; }
    public T GetValueOrDefault();
    public T GetValueOrDefault(T defaultValue);
    public override bool Equal(object other);
    public override int GetHashCode();
    public override string ToString();
    public static implicit operator T?(T value);
    public static explicit operator T(T? value);
}

同時Nullable實現相應的Equals、ToString、GetHashCode方法,以及顯式和隱式對原始值類型與可空類型的轉換。因此,在本質上Nullable可以看成是預定義的struct類型,創建一個Nullable類型的IL表示可以非常清晰的提供例證,例如創建一個值爲int型的可空類型過程。

可空類型小結:

  • 可空類型表示值爲null的值類型。
  • 不允許使用嵌套的可空類型,例如Nullable<Nullable>。
  • Nullable和 T? 是等效的。
  • 對可空類型執行GetType方法,將返回類型T,而不是Nullable。
  • c#允許在可空類型上執行轉換和轉型,例如:
int? a = 100;
Int32 b = (Int32)a;
a = null;
  • 同時爲了更好的將可空類型與原有的類型系統進行兼容,CLR提供了對可空類型裝箱和拆箱的支持。

??運算符
在實際的開發中,爲了有效避免發生異常情況,進行null判定是經常發生的事情,例如對於任意對象執行ToString()操作,都應該進行必要的null檢查,以免發生不必要的異常提示,我們常常是這樣實現的:

object obj = new object();
string objName = string.Empty;
if(obj != null)
{
    objName = obj.ToString();
}
Console.Write(objName);

但是以上這種方法是不太好的,下面是用(? : )三元運算符:

object obj = new object();
string objName = obj == null ? string.Empty : obj.ToString();
Console.Write(objName);

上述obj可以代表任意的自定義類型對象,你可以通過覆寫ToString()方法來輸出你想要的輸出結果,但是在.NET3.0中提供了新的操作運算符來簡化null值的判斷過程,就是 : ??運算符。例下:

object = null;
string objName = (obj ?? string.Empty).ToString();
Console.Write(objName);

??運算符,又稱爲null-coalescing operator,如果左側操作數爲null,則返回右側操作數的值,如果不爲null則返回左側操作數的值。它既可以應用於可空類型,又可以應用於引用類型。

Null Object模式

解決什麼問題? null object模式就是爲對象提供一個指定的類型,來代替對象爲空的情況。說白了就是解決對象爲空的情況,提供對象“什麼也不做”的行爲,舉例,一個User類型對象user需要在系統中進行操作,那麼典型的操作方式是:

if(user != null)
{
manager.SendMessage(user);
}

這種類似的操作,會遍佈於你的系統代碼,無數的if判斷讓優雅離你越來越遠,如果忘記了null判斷,那麼只有無情的異常伺候了。於是,Null Object模式就應運而生了,對User類實現相同功能的NullUser類型,就可以有效的避免繁瑣的if和不必要的判斷失誤:

public class NullUser: IUser
{
    public void Login()
    {
        //不做任何處理
    }
    public void GetInfo(){ }
    public bool IsNull
    {
        get { return true; }
    }
}

IsNull屬性用於提供統一判定null方式,如果對象爲NullUser實例,那麼IsNull一定是true。那麼,二者的差別體現在哪兒?其實主要的思路就是將null value 轉換爲 null object ,把對user == null 這樣的判斷,轉換爲user.IsNull 雖然只有一字之差,但是本質上是兩回事。

下面是一種較爲通用的null object 模式方案,並將其實現爲具有.NET特殊的null object ,所以採取實現.NET中INullable接口的方式來實現,INullable接口是一個包括了IsNull屬性的接口,其定義爲:

public interface INullable
{
    bool IsNull { get; }
}

上圖舉例了簡單的幾個方法和屬性,其中User的定義爲:

public class User : IUser
{
    public void Login()
    {
        Console.Write("User Login Now.");
    }
    public void GetInfo()
    {
        Console.Write("User Logout Now.");
    }
    public bool IsNull
    {
        get { return false; }
    }
}
public class NullUser: IUser
{
    public void Login()
    {
        //不做任何處理
    }
    public void GetInfo(){ }
    public bool IsNull
    {
        get { return true; }
    }
}

//同時通過UserManager類來完成對User的操作和管理,基於對null object 的引入,實現的方式可以爲:
class UserManager
{
    private IUser user  = new User();
    public IUser User
    {
        get { return user; }
        set 
        {
            user = value ?? new NullUser():
        }
    }
}
//下面是有效的測試
public static void Main()
{
    UserNanager manager = new UserManager();
    //強制爲null
    manager.User = null;
    //執行正常
    manager.User.Login();
    if(manager.User.IsNull)
    {
        Console.Write("用戶不存在,請檢查。");
    }
}

null object模式小結:

  • 有效解決對象爲空的情況,爲值爲null提供可靠保證。
  • 保證能夠返回有效的默認值
  • 提供統一判定的IsNull屬性,可以通過實現INullable接口,也可以通過Extension Method實現 IsNull判斷方法。
  • null object 要保持原 object的所有成員的不變性,所以我們常常將其實現爲 Sigleton模式。
  • Scott Doman說“在執行方法時返回 null object 而不是 null 值,可以避免 NullReferenceException異常的發生”,這完全是對的。

摘自《你必須知道的.NET》第二十一回 認識全面的null

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章