C#6 null 前提運算符。本站提示廣大學習愛好者:(C#6 null 前提運算符)文章只能為提供參考,不一定能成為您想要的結果。以下是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為什麼會被編譯成支撐多態方法挪用的callvirt,在這類情形下貌似call效力會更高一些,然則畢竟"?."和"?:"編譯的代碼沒有實質差別。
然則和if斷定的比擬簡化了一些,我們剖析下IL,看看有哪些差別(這裡就疏忽call和callvirt的差別了):
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 //比擬前兩步加載的值。假如第一個值年夜於第二個值,則將整數值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() //挪用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() ////挪用person對象的get_Name辦法。 IL_000f: stloc.1 //把上一步的成果存入索引為1的變量中,也就是name對象。 IL_0010: ret //前往 }
經由過程剖析我們發明,null運算符編譯後的IL代碼更冗長,應用了2個分支跳轉,簡化了斷定邏輯,而if版的IL還多出來一個bool類型的V_2暫時變量。
so,結論就是"?."的和三元運算符"?:"的編譯成果是一樣的,並且簡化了if的斷定。所以不論是從機能照樣可讀性方面斟酌,"?."都是推舉的寫法。
3. Example 3.1 ?[
null前提運算符不只可使用?.的語法拜訪對象的屬性和辦法,還可以用?[ 的語法拜訪檢測數組或包括索引器的對象能否是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 以線程平安的方法挪用事宜
PropertyChangedEventHandler propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged(this, new PropertyChangedEventArgs(nameof(Name))); }
下面的代碼一向是我們挪用事宜的處置方法,把事宜的援用放到一個暫時變量中是為了避免在挪用這個拜托的時刻,事宜被撤消注冊,發生null的情形。
我們從C#6今後終究可以用更簡略的方法去觸發事宜挪用了(這個埂自從C#1時期一向延續至今...):
PropertyChanged?.Invoke(propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
4. 總結
null前提運算符是一種語法簡化,同時也會做一種編譯優化,優化方法和三元運算符的優化後果是分歧的。語法更簡化了,機能也更好了,我們有甚麼來由不消新語法呢。