C6 null Conditional Operator

  • 2021-11-01 04:19:07
  • OfStack

1. Older versions of the code


 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;
   }
  }
 }
 }

When we use the properties of an object, sometimes the first step is to judge whether the object itself is bull, otherwise you may get an exception of System. NullReferenceException. Although sometimes we can use the 3-element operator string name = person! = null? person. Name: null; To simplify the code, but this writing method is still not simple enough... Because of a coding behavior commonly used in programming when null value detection, so and C # 6 bring us a more simplified way.

2. null conditional operator


 namespace csharp6
 {
  internal class Person
  {
   public string Name { get; set; }
  }
 
  internal class Program
  {
   private static void Main()
   {
    Person person = null;
   string name = person?.Name;
  }
  }
 }

We can see from the above that using? This method can replace if judgment and simplify the use of 3-element operators, concise to can not be more concise. By convention, the last two IL codes are compared.

Old version of IL code:


.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

IL version of if

IL for the new syntax:


.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

IL Conditional Operator Version of null

Hey, it seems that there is a big difference. Let's take a look at IL of 3 yuan operator version:


 .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 of 3 yuan operator version

New grammar "? . "And the 3-yuan operator"? The result is that the only difference of 1 is the 1 line of IL_000a. "? . "is compiled into call, while"? : "The way was compiled into callvirt, somehow"? : "Why is persion. Name compiled into callvirt that supports polymorphic invocation? In this case, it seems that call will be more efficient, but after all"? . "And"? : "There is no essential difference in compiled code.

However, compared with the judgment of if, it is simplified by 1. Let's analyze IL to see what differences exist (the difference between call and callvirt is ignored here):

IL analysis of if version:


.method private hidebysig static void Main() cil managed
 {
 .entrypoint
 .maxstack 2
 .locals init ([0] class csharp6.Person person, // Initialize local variables person, Put person Put the index as 0 Location of 
   [1] string name,      // Initialize local variables name, Put name Put the index as 1 Location of 
   [2] bool V_2)       // Initialize local variables V_2, Put V_2 Put the index as 2 Location of 
 IL_0000: nop         // Empty 
 IL_0001: ldnull        // Loading null
 IL_0002: stloc.0        // Put null Put the index as 0 The variable of, that is, person Object. 
 IL_0003: ldnull        // Loading null
 IL_0004: stloc.1        // Put null Put the index as 1 The variable of, that is, name Object. 
 IL_0005: ldloc.0        // Load index as 0 The variable of the position of, that is, person Object 
 IL_0006: ldnull        // Loading null
 IL_0007: cgt.un        // Compare the values loaded in the previous two steps. If the 1 The value is greater than the number 2 Value, the integer value is set 1 Push to the calculation stack; On the contrary, it will 0 To the evaluation stack. 
 IL_0009: stloc.2        // Put the comparison results into the index as 2 In the variable of, that is, V_2 Object 
 IL_000a: ldloc.2        // Load index as 2 The object of, that is, V_2 Object 
 IL_000b: brfalse.s IL_0016     // If on 1 The object loaded by the step is false Null reference or zero, jump to IL_0016 Location, that is, the end of the current method. 
 IL_000d: nop         // Empty 
 IL_000e: ldloc.0        // Load index as 0 The variable of the position of, that is, person Object 
 IL_000f: callvirt instance string csharp6.Person::get_Name() // Call person Object's get_Name Method. 
 IL_0014: stloc.1        // Put it on 1 The result of the step is stored in the index as 1 In the variable of, that is, name Object. 
 IL_0015: nop         // Empty 
 IL_0016: ret         // Return 
 } 

IL analysis of null conditional operator version:


 .method private hidebysig static void Main() cil managed
 {
  .entrypoint
  .maxstack 1
  .locals init ([0] class csharp6.Person person, // Initialize local variables person, Put person Put the index as 0 Location of 
       [1] string name)           // Initialize local variables name, Put name Put the index as 1 Location of 
  IL_0000: nop                 // Empty 
  IL_0001: ldnull                // Loading null
  IL_0002: stloc.0               // Put null Put the index as 0 The variable of, that is, person Object 
  IL_0003: ldloc.0               // Load index as 0 The variable of the position of, that is, person Object 
  IL_0004: brtrue.s  IL_0009          // If on 1 The object loaded by the step is true Is a non-null reference or non-zero, jump to IL_0009 Position 
  IL_0006: ldnull                // Loading null
  IL_0007: br.s    IL_000f          // Jump unconditionally to IL_000f Department 
  IL_0009: ldloc.0               // Load index as 0 The variable of the position of, that is, person Object 
  IL_000a: call    instance string csharp6.Person::get_Name() //// Call person Object's get_Name Method. 
  IL_000f: stloc.1               // Put it on 1 The result of the step is stored in the index as 1 In the variable of, that is, name Object. 
  IL_0010: ret                 // Return 
 }

Through analysis, we find that the IL code compiled by null operator is shorter, using two branch jumps, which simplifies the judgment logic, and there is an extra V_2 temporary variable of bool type in if version of IL.

so, the conclusion is "? . "And 3 yuan operators"? The compilation result of: "is 1, and it simplifies the judgment of if. So both in terms of performance and readability, "? . "is the recommended way to write.

3. Example 3.1 ?[

The null conditional operator not only can be used? Access the properties and methods of objects with the syntax of., and you can also use? Detects whether an array or an object containing an indexer is an null. For example:


 Person[] persons = null;
 //?.
 int? length = persons?.Length;
 //?[
 Person first = persons?[0];

3.2? . Combine? ?

persions above? The result returned by Lenght is of type Nullable. Sometimes we may need an int, so we can combine the empty join operator "? ? "1 use, such as:

Person[] persons = null;
//? . And? ? Combined use
int length = persons?.Length ?? 0;

3.3 Invoking Events in a Thread-Safe Way


 PropertyChangedEventHandler propertyChanged = PropertyChanged;
 if (propertyChanged != null)
 {
 propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
 }

The above code 1 is the processing method of calling events, and putting the reference of events into a temporary variable is to prevent the events from being unregistered when calling this delegate, resulting in null.

After C # 6, we can finally trigger event calls in a simpler way (this ridge has continued since C # 1 era 1...):

PropertyChanged?.Invoke(propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));

4. Summary

null conditional operator is a syntax simplification, but also do a compilation optimization, optimization and 3 operator optimization effect is 1. The syntax is simpler and the performance is better. Why don't we use the new syntax?


Related articles: