021-認識全面的null
null
nullable
??運算符
null object
從什麼是null開始?
-
null,一個值得尊敬的數據標識。
-
一般來說,null表示空類型,也就是表示什麼都沒有,但是“什麼都沒有”並不意味着“什麼都不是”。實際上,null是如此重要,以至於在JavaScript中,Null類型就作爲5中基本的原始類型之一,與Undefined、Boolean、Number和String並駕齊驅。但是null並不等同於0,"",string.Empty這些通常意義上的“零”值概念。相反,null具有實實在在的意義,這個意義就是用於標識變量引用的一種狀態,這種狀態表示沒有引用任何對象實例,也就是表示“什麼都沒有”,即不是Object實例,也不是User實例,而是一個空引用而已。
-
在.NET中,null表示一個對象引用是無效的。作爲引用類型變量的默認值,null是針對指針(引用)而言的,他是引用類型變量的專屬概念,表示一個引用類型變量聲明但未初始化的狀態。例子如下i。此時obj僅僅是一個保存在線程棧上的引用指針,不代表任何意義,obj未指向任何有效實例,而被默認初始化爲null。
例如:object obj = null; -
object obj 和 object obj = null 的區別?
主要體現在編譯器的檢查上,默認情況下,創建一個引用類型變量時,CLR即將其初始化爲null,表示不指向任何有效實例,所以本質上二者表示了相同的意義,但是有所區別。
-
在.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