C#6 null 條件運算符
1. 老版本的代碼
namespace csharp6 { internal class Person { public string Name { get; set; } } internal class Program { private static void Main() { Person person = null; string name = null; if (person != null) { name = person.Name; } } } }
在我們使用一個對象的屬性的時候,有時候第一步需要做的事情是先判斷這個對象本身是不是bull,不然的話你可能會得到一個System.NullReferenceException 的異常。雖然有時候我們可以使用三元運算符string name = person != null ? person.Name : null;來簡化代碼,但是這種書寫方式還是不夠簡單......由于null值檢測時編程中非常常用的一種編碼行為,so,C#6為我們帶來了一種更為簡化的方式。
2. null條件運算符
namespace csharp6 { internal class Person { public string Name { get; set; } } internal class Program { private static void Main() { Person person = null; string name = person?.Name; } } }
從上面我們可以看出,使用?. 這種方式可以代替if判斷和簡化三元運算符的使用,簡潔到不能再簡潔了吧。按照慣例,上兩份IL代碼對比對比。
老版本的IL代碼:
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 23 (0x17) .maxstack 2 .locals init ([0] class csharp6.Person person, [1] string name, [2] bool V_2) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 IL_0003: ldnull IL_0004: stloc.1 IL_0005: ldloc.0 IL_0006: ldnull IL_0007: cgt.un IL_0009: stloc.2 IL_000a: ldloc.2 IL_000b: brfalse.s IL_0016 IL_000d: nop IL_000e: ldloc.0 IL_000f: callvirt instance string csharp6.Person::get_Name() IL_0014: stloc.1 IL_0015: nop IL_0016: ret } // end of method Program::Main
if版的IL
新語法的IL:
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 17 (0x11) .maxstack 1 .locals init ([0] class csharp6.Person person, [1] string name) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: brtrue.s IL_0009 IL_0006: ldnull IL_0007: br.s IL_000f IL_0009: ldloc.0 IL_000a: call instance string csharp6.Person::get_Name() IL_000f: stloc.1 IL_0010: ret } // end of method Program::Main
null條件運算符版的IL
咦,貌似有很大不一樣,我們再來一份三元運算符版的IL看看:
.method private hidebysig static void Main() cil managed { .entrypoint // Code size 17 (0x11) .maxstack 1 .locals init ([0] class csharp6.Person person, [1] string name) IL_0000: nop IL_0001: ldnull IL_0002: stloc.0 IL_0003: ldloc.0 IL_0004: brtrue.s IL_0009 IL_0006: ldnull IL_0007: br.s IL_000f IL_0009: ldloc.0 IL_000a: callvirt instance string csharp6.Person::get_Name() IL_000f: stloc.1 IL_0010: ret } // end of method Program::Main
三元運算符版的IL
新語法"?."和三元運算符"?:"的結果是唯一的差別是IL_000a這一行。"?."的方式被編譯為call,而"?:"的方式被編譯為callvirt,不知為何"?:"中的persion.Name為何會被編譯成支持多態(tài)方式調(diào)用的callvirt,在這種情況下貌似call效率會更高一些,但是終究"?."和"?:"編譯的代碼沒有本質差異。
但是和if判斷的相比簡化了一些,我們分析下IL,看看有哪些差異(這里就忽略call和callvirt的區(qū)別了):
if版的IL分析:
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ([0] class csharp6.Person person, //初始化局部變量person,把person放在索引為0的位置 [1] string name, //初始化局部變量name,把name放在索引為1的位置 [2] bool V_2) //初始化局部變量V_2,把V_2放在索引為2的位置 IL_0000: nop //空 IL_0001: ldnull //加載null IL_0002: stloc.0 //把null放入索引為0的變量,也就是person對象。 IL_0003: ldnull //加載null IL_0004: stloc.1 //把null放入索引為1的變量,也就是name對象。 IL_0005: ldloc.0 //加載索引為0的位置的變量,也就是person對象 IL_0006: ldnull //加載null IL_0007: cgt.un //比較前兩步加載的值。如果第一個值大于第二個值,則將整數(shù)值1推送到計算堆棧上;反之,將0推送到計算堆棧上。 IL_0009: stloc.2 //把比較結果放入索引為2的變量中,也就是V_2對象 IL_000a: ldloc.2 //加載索引為2的對象,也就是V_2對象 IL_000b: brfalse.s IL_0016 //如果上一步加載的對象為false、空引用或零,則跳轉到IL_0016位置,也就是結束當前方法。 IL_000d: nop //空 IL_000e: ldloc.0 //加載索引為0的位置的變量,也就是person對象 IL_000f: callvirt instance string csharp6.Person::get_Name() //調(diào)用person對象的get_Name方法。 IL_0014: stloc.1 //把上一步的結果存入索引為1的變量中,也就是name對象。 IL_0015: nop //空 IL_0016: ret //返回 }
null條件運算符版的IL分析:
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 1 .locals init ([0] class csharp6.Person person, //初始化局部變量person,把person放在索引為0的位置 [1] string name) //初始化局部變量name,把name放在索引為1的位置 IL_0000: nop //空 IL_0001: ldnull //加載null IL_0002: stloc.0 //把null放入索引為0的變量,也就是person對象 IL_0003: ldloc.0 //加載索引為0的位置的變量,也就是person對象 IL_0004: brtrue.s IL_0009 //如果上一步加載的對象為true、非空引用或非零,則跳轉到IL_0009位置 IL_0006: ldnull //加載null IL_0007: br.s IL_000f //無條件的跳轉到IL_000f處 IL_0009: ldloc.0 //加載索引為0的位置的變量,也就是person對象 IL_000a: call instance string csharp6.Person::get_Name() ////調(diào)用person對象的get_Name方法。 IL_000f: stloc.1 //把上一步的結果存入索引為1的變量中,也就是name對象。 IL_0010: ret //返回 }
通過分析我們發(fā)現(xiàn),null運算符編譯后的IL代碼更簡短,使用了2個分支跳轉,簡化了判斷邏輯,而if版的IL還多出來一個bool類型的V_2臨時變量。
so,結論就是"?."的和三元運算符"?:"的編譯結果是一樣的,而且簡化了if的判斷。所以不管是從性能還是可讀性方面考慮,"?."都是推薦的寫法。
3. Example 3.1 ?[
null條件運算符不但可以使用?.的語法訪問對象的屬性和方法,還可以用?[ 的語法訪問檢測數(shù)組或包含索引器的對象是否是null。比如:
Person[] persons = null; //?. int? length = persons?.Length; //?[ Person first = persons?[0];
3.2 ?.結合??
上面的persions?.Lenght返回的結果是Nullable類型的,有時候我們可能需要的是一個int類型的,那么我們可以結合空連接運算符"??"一起使用,比如:
Person[] persons = null;
//?.和??結合使用
int length = persons?.Length ?? 0;
3.3 以線程安全的方式調(diào)用事件
PropertyChangedEventHandler propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs(nameof(Name))); }
上面的代碼一直是我們調(diào)用事件的處理方式,把事件的引用放到一個臨時變量中是為了防止在調(diào)用這個委托的時候,事件被取消注冊,產(chǎn)生null的情況。
我們從C#6以后終于可以用更簡單的方式去觸發(fā)事件調(diào)用了(這個埂自從C#1時代一直延續(xù)至今...):
PropertyChanged?.Invoke(propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
4. 總結
null條件運算符是一種語法簡化,同時也會做一種編譯優(yōu)化,優(yōu)化方式和三元運算符的優(yōu)化效果是一致的。語法更簡化了,性能也更好了,我們有什么理由不用新語法呢。
相關文章
C#中DataTable的創(chuàng)建與遍歷實現(xiàn)
這篇文章主要介紹了C#中DataTable的創(chuàng)建與遍歷實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02C#創(chuàng)建簡單windows窗體應用(加法器)
這篇文章主要為大家詳細介紹了C#創(chuàng)建一個簡單windows窗體應用的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03jQuery uploadify在谷歌和火狐瀏覽器上傳失敗的解決方案
jquery.uploadify插件是一個基于jquery來實現(xiàn)上傳的,這個插件很好用,每一次向后臺發(fā)送數(shù)據(jù)流請求時,ie會自動把本地cookie存儲捆綁在一起發(fā)送給服務器。但firefox、chrome不會這樣做,他們會認為這樣不安全,下面介紹下jQuery uploadify上傳失敗的解決方案2015-08-08C#實現(xiàn)判斷操作系統(tǒng)是否為Win8以上版本
這篇文章主要介紹了C#實現(xiàn)判斷操作系統(tǒng)是否為Win8以上版本,本文講解了利用C#獲取OS的版本號、利用反射獲取當前正在運行的程序的版本信息、 利用C#判斷當前操作系統(tǒng)是否為Win8系統(tǒng)等內(nèi)容,需要的朋友可以參考下2015-06-06Winform實現(xiàn)抓取web頁面內(nèi)容的方法
這篇文章主要介紹了Winform實現(xiàn)抓取web頁面內(nèi)容的方法,代碼只有短短幾行,但是功能很實用,需要的朋友可以參考下2014-09-09C# 實現(xiàn)把double 存成兩位精度小數(shù)
這篇文章主要介紹了C# 實現(xiàn)把double 存成兩位精度小數(shù),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12