【转】[你必须知道的.NET]第二十一回:认识全面的null
?
?
引用自:http://www.cnblogs.com/anytao/category/155694.html
作者:Anytao
。
| 說在,開篇之前 |
| null、 nullable、??運算符、null object模式,這些閃亮的概念在你眼前晃動,我們有理由相信“存在即合理”,事實上,null不光合理,而且重要。本文,從null的基本認知開始, 逐層了解可空類型、??運算符和null object模式,在循序之旅中了解不一樣的null。 你必須知道的.NET,繼續全新體驗,分享更多色彩。 ?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? www.anytao.com |
?
1 從什么是null開始?
null,一個值得尊敬的數據標識。
一般說來,null表示空類型,也就是表示什么都沒有,但是“什么都沒有”并不意味“什么都不是”。實際上,null是如此的重要,以致于在 JavaScript中,Null類型就作為5種基本的原始類型之一,與Undefined、Boolean、Number和String并駕齊驅。這種 重要性同樣表現在.NET中,但是一定要澄清的是,null并不等同于0,"",string.Empty這些通常意義上的“零”值概念。相反,null 具有實實在在的意義,這個意義就是用于標識變量引用的一種狀態,這種狀態表示沒有引用任何對象實例,也就是表示“什么都沒有”,既不是Object實例,也不是User實例,而是一個空引用而已。
在上述讓我都拗口抓狂的表述中,其實中心思想就是澄清一個關于null意義的無力訴說,而在.NET中null又有什么實際的意義呢?
在.NET中,null表示一個對象引用是無效的。作為引用類型變量的默認值,null是針對指針(引用)而言的,它是引用類型變量的專屬概念,表示一個引用類型變量聲明但未初始化的狀態,例如:
??????????? object obj = null;
此時obj僅僅是一個保存在線程棧上的引用指針,不代表任何意義,obj未指向任何有效實例,而被默認初始化為null。
object obj和object obj = null的區別?
那么,object obj和object obj = null有實際的區別嗎?答案是:有。主要體現在編譯器的檢查上。默認情況下,創建一個引用類型變量時,CLR即將其初始化為null,表示不指向任何有效實例,所以本質上二者表示了相同的意義,但是有有所區別:
??????????? // Copyright?? : www.anytao.com???????
??????????? // Author????? : Anytao,http://www.anytao.com???????
??????????? // Release???? : 2008/07/31 1.0
?
??????????? //編譯器檢測錯誤:使用未賦值變量obj
??????????? //object obj;
?
????? ??????//編譯器理解為執行了初始化操作,所以不引發編譯時錯誤
??????????? object obj = null;
???????????
??????????? if (obj == null)
??????????? {
??????????????? //運行時拋出NullReferenceException異常
??????????????? Console.WriteLine(obj.ToString());
??????????? }
注:當我把這個問題拋給幾個朋友時,對此的想法都未形成統一的共識,幾位同志各有各的理解,也各有個的道理。當然,我也慎重的對此進行了一番探討和分析,但是并未形成完全100%確定性的答案。不過,在理解上我更傾向于自己的分析和判斷,所以在給出上述結論的基礎上,也將這個小小的思考留給大家來探討,好的思考和分析別忘了留給大家。事實上,將
??????? static void Main(string[] args)
??????? {
??????????? object o;
??????????? object obj = null;
??????? }
反編譯為IL時,二者在IL層還是存在一定的差別:
.method private hidebysig static void Main(string[] args) cil managed
{
??? .entrypoint
??? .maxstack 1
??? .locals init (
??????? [0] object o,
??????? [1] object obj)
??? L_0000: nop
??? L_0001: ldnull
??? L_0002: stloc.1
??? L_0003: ret
}
前者沒有發生任何附加操作;而后者通過ldnull指令推進一個空引用給evaluation stack,而stloc則將空引用保存。
回到規則
在.NET中,對null有如下的基本規則和應用:
- null為引用類型變量的默認值,為引用類型的概念范疇。
- null不等同于0,"",string.Empty。
- 引用is或as模式對類型進行判斷或轉換時,需要做進一步的null判斷。
?
| 快捷參考 |
?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? www.anytao.com |
- 判斷一個變量是否為null,可以應用==或!=操作符來完成。
- 對任何值為nul的l變量操作,都會拋出NullReferenceException異常。
?
2 Nullable<T>(可空類型)
?
一直以來,null都是引用類型的特有產物,對值類型進行null操作將在編譯器拋出錯誤提示,例如:
??????????? //拋出編譯時錯誤
??????????? int i = null;?
??????????? if (i == null)
??????????? {
??????? ????????Console.WriteLine("i is null.");
??????????? }
正如示例中所示,很多情況下作為開發人員,我們更希望能夠以統一的方式來處理,同時也希望能夠解決實際業務需求中對于“值”也可以為“空”這一實際 情況的映射。因此,自.NET 2.0以來,這一特權被新的System.Nullable<T>(即,可空值類型)的誕生而打破,解除上述詬病可以很容易以下面的方式被實現:
??????????? //Nullable<T>解決了這一問題
??????????? int? i = null;
??????????? if (i == null)
??????????? {
??????????????? Console.WriteLine("i is null.");
??????????? }
你可能很奇怪上述示例中并沒有任何Nullable的影子,實際上這是C#的一個語法糖,以下代碼在本質上是完全等效的:
??????????? int? i = null;
??????????? Nullable<int> i = null;
顯然,我們更中意以第一種簡潔而優雅的方式來實現我們的代碼,但是在本質上Nullable<T>和T?他們是一路貨色。
可空類型的偉大意義在于,通過Nullable<T>類型,.NET為值類型添加“可空性”,例如 Nullable<Boolean>的值就包括了true、false和null,而Nullable<Int32>則表示值即可以為整形也可以為null。同時,可空類型實現了統一的方式來處理值類型和引用類型的“空”值問題,例如值類型也可以享有在運行時以 NullReferenceException異常來處理。
另外,可空類型是內置于CLR的,所以它并非c#的獨門絕技,VB.NET中同樣存在相同的概念。
Nullable的本質(IL)
那么我們如何來認識Nullable的本質呢?當你聲明一個:
??????????? Nullable<Int32> count = new Nullable<Int32>();
時,到底發生了什么樣的過程呢?我們首先來了解一下Nullable在.NET中的定義:
??? public struct Nullable<T> where T : struct
??? {
??????? private bool hasValue;
??????? internal T value;
??????? public Nullable(T value);
??????? public bool HasValue { get; }
??????? public T Value { get; }
??????? public T GetValueOrDefault();
??????? public T GetValueOrDefault(T defaultValue);
??????? public override bool Equals(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本質上仍是一個struct為值類型,其實例對象仍然分配在線程棧上。其中的value屬性封裝了具體的值類型,Nullable<T>進行初始化時,將值類型賦給value,可以從其構造函數獲知:
??????? public Nullable(T value)
??????? {
??????????? this.value = value;
??????????? this.hasValue = true;
??????? }
同時Nullable<T>實現相應的Equals、ToString、GetHashCode方法,以及顯式和隱式對原始值類型與可空類型的轉換。因此,在本質上Nullable可以看著是預定義的struct類型,創建一個Nullable<T>類型的IL表示可以非常 清晰的提供例證,例如創建一個值為int型可空類型過程,其IL可以表示為:
??? .method private hidebysig static void Main() cil managed
??? {
??????? .entrypoint
??????? .maxstack 2
??????? .locals init (
??????????? [0] valuetype [mscorlib]System.Nullable`1<int32> a)
??????? L_0000: nop
??????? L_0001: ldloca.s a
??????? L_0003: ldc.i4 0x3e8
??????? L_0008: call instance void [mscorlib]System.Nullable`1<int32>::.ctor(!0)
??????? L_000d: nop
??????? L_000e: ret
??? }
對于可空類型,同樣需要必要的小結:
- 可空類型表示值為null的值類型。
- 不允許使用嵌套的可空類型,例如Nullable<Nullable<T>> 。
- Nullable<T>和T?是等效的。
- 對可空類型執行GetType方法,將返回類型T,而不是Nullable<T>。
- c#允許在可空類型上執行轉換和轉型,例如:
??????????? int? a = 100;
??????????? Int32 b = (Int32)a;
??????????? a = null;
- 同時為了更好的將可空類型于原有的類型系統進行兼容,CLR提供了對可空類型裝箱和拆箱的支持。
?
?
3 ??運算符
在實際的程序開發中,為了有效避免發生異常情況,進行null判定是經常發生的事情,例如對于任意對象執行ToString()操作,都應該進行必要的null檢查,以免發生不必要的異常提示,我們常常是這樣實現的:
??????????? object obj = new object();
?
??????????? string objName = string.Empty;
??????????? if (obj != null)
??????????? {
??????????????? objName = obj.ToString();
??????????? }
?
??????????? Console.WriteLine(objName);
然而這種實現實在是令人作嘔,滿篇的if語句總是讓人看著渾身不適,那么還有更好的實現方式嗎,我們可以嘗試(? :)三元運算符:
??????????? object obj = new object();
??????????? string objName = obj == null ? string.Empty : obj.ToString();
??????????? Console.WriteLine(objName);
上述obj可以代表任意的自定義類型對象,你可以通過覆寫ToString方法來輸出你想要輸出的結果,因為上述實現是如此的頻繁,所以.NET 3.0中提供了新的操作運算符來簡化null值的判斷過程,這就是:??運算符。上述過程可以以更加震撼的代碼表現為:
??????????? // Copyright?? : www.anytao.com???????
??????????? // Author????? : Anytao,http://www.anytao.com???????
??????????? // Release???? : 2008/07/31 1.0
?
??????????? object obj = null;
??????????? string objName = (obj ?? string.Empty).ToString();
??????????? Console.WriteLine(objName);
那么??運算符的具體作用是什么呢?
??運算符,又稱為null-coalescing operator,如果左側操作數為null,則返回右側操作數的值, 如果不為null則返回左側操作數的值。它既可以應用于可空類型,有可以應用于引用類型。
| 插播廣告,我的新書 |
| ? |
4 Nulll Object模式
模式之于設計,正如秘笈之于功夫。正如我們前文所述,null在程序設計中具有舉足輕重的作用,因此如何更優雅的處理“對象為空”這一普遍問題,大 師們提出了Null Object Pattern概念,也就是我們常說的Null Object模式。例如Bob大叔在《敏捷軟件開發--原則、模式、實踐》一書,Martin Fowler在《Refactoring: Improving the Design of Existing Code》一書,都曾就Null Object模式展開詳細的討論,可見23中模式之外還是有很多設計精髓,可能稱為模式有礙經典。但是仍然值得我們挖據、探索和發現。
下面就趁熱打鐵,在null認識的基礎上,對null object模式進行一點探討,研究null object解決的問題,并提出通用的null object應用方式。
解決什么問題?
簡單來說,null object模式就是為對象提供一個指定的類型,來代替對象為空的情況。說白了就是解決對象為空的情況,提供對象“什么也不做”的行為,這種方式看似無 聊,但卻是很聰明的解決之道。舉例來說,一個User類型對象user需要在系統中進行操作,那么典型的操作方式是:
??????????? if (user != null)
??????????? {
?????? ?????????manager.SendMessage(user);
??????????? }
這種類似的操作,會遍布于你的系統代碼,無數的if判斷讓優雅遠離了你的代碼,如果大意忘記null判斷,那么只有無情的異常伺候了。于 是,Null object模式就應運而生了,對User類實現相同功能的NullUser類型,就可以有效的避免繁瑣的if和不必要的失誤:
??? // Copyright?? : www.anytao.com???????
??? // Author????? : Anytao,http://www.anytao.com???????
??? // Release???? : 2008/07/31 1.0
?
??? 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模式,可以確保返回有效的對象,而不是沒有任何意義的null值。同時,“在執行方法時返回null object而不是null值,可以避免NullReferenceExecption異常的發生。”,這是來自Scott Dorman的聲音。
通用的null object方案
下面,我們實現一種較為通用的null object模式方案,并將其實現為具有.NET特色的null object,所以我們采取實現.NET中INullable接口的方式來實現,INullable接口是一個包括了IsNull屬性的接口,其定義為:
??? public interface INullable
??? {
??????? // Properties
??????? bool IsNull { get; }
??? }
仍然以User類為例,實現的方案可以表達為:
?
圖中僅僅列舉了簡單的幾個方法或屬性,旨在達到說明思路的目的,其中User的定義為:
??? // Copyright?? : www.anytao.com???????
??? // Author????? : Anytao,http://www.anytao.com???????
??? // Release???? : 2008/07/31 1.0
?
??? public class User : IUser
??? {
??????? public void Login()
??????? {
??????????? Console.WriteLine("User Login now.");
??????? }
?
??????? public void GetInfo()
??????? {
??????????? Console.WriteLine("User Logout now.");
??????? }
?
??? ????public bool IsNull
??????? {
??????????? get { return false; }
??????? }
??? }
而對應的NullUser,其定義為:
??? // Copyright?? : www.anytao.com???????
??? // Author????? : Anytao,http://www.anytao.com???????
??? // Release???? : 2008/07/31 1.0
?
??? public class NullUser : IUser
??? {
??????? public void Login()
??????? {
??????????? //不做任何處理
??????? }
?
??????? public void GetInfo() { }
?
??????? public bool IsNull
??????? {
??????????? get { return true; }
??????? }
??? }
同時通過UserManager類來完成對User的操作和管理,你很容易思考通過關聯方式,將IUser作為UserManger的屬性來實現,基于對null object的引入,實現的方式可以為:
??? // Copyright?? : www.anytao.com???????
??? // Author????? : Anytao,http://www.anytao.com???????
??? // Release???? : 2008/07/31 1.0
?
??? class UserManager
??? {
??????? private IUser user = new User();
?
??????? public IUser User
??????? {
??????????? get { return user; }
??????????? set
??????????? {
??????????????? user = value ?? new NullUser();
??????????? }
??????? }
??? }
當然有效的測試是必要的:
??????? public static void Main()
??????? {
?????? ?????UserManager manager = new UserManager();
??????????? //強制為null
??????????? manager.User = null;
??????????? //執行正常
??????????? manager.User.Login();
?
??????????? if (manager.User.IsNull)
??????????? {
??????????????? Console.WriteLine("用戶不存在,請檢查。");
??????????? }
??????? }
通過強制將User屬性實現為null,在調用Login時仍然能夠保證系統的穩定性,有效避免對null的判定操作,這至少可以讓我們的系統少了很多不必要的判定代碼。
詳細的代碼可以通過本文最后的下載空間進行下載。實際上,可以通過引入Facotry Method模式來構建對于User和NullUser的創建工作,這樣就可以完全消除應用if進行判斷的僵化,不過那是另外一項工作罷了。
當然,這只是null object的一種實現方案,在此對《Refactoring》一書的示例進行改良,完成更具有.NET特色的null object實現,你也可以請NullUser繼承Use并添加相應的IsNull判定屬性來完成。
借力c# 3.0的Null object
在C# 3.0中,Extension Method(擴展方法)對于成就LINQ居功至偉,但是Extension Method的神奇遠不是止于LINQ。在實際的設計中,靈活而巧妙的應用,同樣可以給你的設計帶來意想不到的震撼,以上述User為例我們應用 Extension Method來取巧實現更簡潔IsNull判定,代替實現INullable接口的方法而采用更簡單的實現方式。重新構造一個實現相同功能的擴展方法,例 如:
??? // Copyright?? : www.anytao.com???????
??? // Author????? : Anytao,http://www.anytao.com???????
??? // Release???? : 2008/07/31 1.0
?
??? public static class UserExtension
??? {
??????? public static bool IsNull(this User user)
??????? {
??????????? return null == user;
??????? }
??? }
當然,這只是一個簡單的思路,僅僅將對null value的判斷轉換為null object的判斷角度來看,擴展方法帶來了更有效的、更簡潔的表現力。?
null object模式的小結
- 有效解決對象為空的情況,為值為null提供可靠保證。
- 保證能夠返回有效的默認值,例如在一個IList<User> userList中,能夠保證任何情況下都有有效值返回,可以保證對userList操作的有效性,例如:
??????? // Copyright?? : www.anytao.com???????
??????? // Author????? : Anytao,http://www.anytao.com???????
??????? // Release???? : 2008/07/31 1.0
?
??????? public void SendMessageAll(List<User> userList)
??????? {
???? ???????//不需要對userList進行null判斷
??????????? foreach (User user in userList)
??????????? {
??????????????? user.SendMessage();
??????????? }
??????? }
- 提供統一判定的IsNull屬性。可以通過實現INullable接口,也可以通過Extension Method實現IsNull判定方法。
- null object要保持原object的所有成員的不變性,所以我們常常將其實現為Sigleton模式。
- Scott Doman說“在執行方法時返回null object而不是null值,可以避免NullReferenceExecption異常的發生”,這完全是對的。
5 結論
雖然形色匆匆,但是通過本文你可以基本了解關于null這個話題的方方面面,堆積到一起就是對一個概念清晰的把握和探討。技術的魅力,大概也正是如此而已吧,色彩斑斕的世界里,即便是“什么都沒有”的null,在我看來依然有很多很多。。。值得探索、思考和分享。
還有更多的null,例如LINQ中的null,SQL中的null,仍然可以進行探討,我們將這種思考繼續,所收獲的果實就越多。
總結
以上是生活随笔為你收集整理的【转】[你必须知道的.NET]第二十一回:认识全面的null的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 番茄花园该打,反垄断更该升级
- 下一篇: DNN小技巧