結構二三事 (3)

聊聊 C# 7.2 不可變的結構設計

決戰設計模式第七梯招生中決戰設計模式第七梯招生中

 

C# 7.2 來了一個新東西 readonly struct,就是可以宣告一個不可變的結構,大概長這樣子:

程式碼 1
 public readonly struct MyStruct
 {
     private readonly int _y;
     public int X { get; }
     public MyStruct(int a)
     {
         X = a;
         _y = a;
     }       
 }

MyStrcuct 被宣告為 readonly struct,表示具有不可變動性 (immutable),因此它的欄位只能宣告為 readonly;屬性也只能有 getter 。也就是說,一旦建立起了 MyStruct 執行個體,它的內部欄位值是無法更改的。

這玩意大致上都是在防止防禦性複製的發生,在結構二三事(1)中提到其中一個狀況就是 readonly field,另外一個狀況則是方法參數宣告使用 in 參數修飾詞以及 readonly ref 的時候,例如:

程式碼 2
  class Program
  {

      static void Main(string[] args)
      {

      }

      void Test(in MyStruct s)
      {
          s.Request();
      }

  }

  public struct MyStruct
  {
      private readonly int _y;
      public int X { get; }
      public MyStruct(int a)
      {
          X = a;
          _y = a;
      }
      public void Request()
      { }
  }

Test 方法在編譯後,會多產生一個臨時區域變數,觀察下方 IL code 的 locals init

.method private hidebysig instance void  Test([in] valuetype ConsoleApp2.MyStruct& s) cil managed
{
  .param [1]
  .custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) 
  // 程式碼大小       17 (0x11)
  .maxstack  1
  .locals init ([0] valuetype ConsoleApp2.MyStruct V_0)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldobj      ConsoleApp2.MyStruct
  IL_0007:  stloc.0
  IL_0008:  ldloca.s   V_0
  IL_000a:  call       instance void ConsoleApp2.MyStruct::Request()
  IL_000f:  nop
  IL_0010:  ret
} // end of method Program::Test

將 MyStruct 宣告為 readonly,程式碼改變如下:

程式碼 3
 class Program
 {

     static void Main(string[] args)
     {

     }

     void Test(in MyStruct s)
     {
         s.Request();
     }

 }

 public readonly struct MyStruct
 {
     private readonly int _y;
     public int X { get; }
     public MyStruct(int a)
     {
         X = a;
         _y = a;
     }
     public void Request()
     { }
 }

在Test方法就不再產生出臨時區域變數。

.method private hidebysig instance void  Test([in] valuetype ConsoleApp2.MyStruct& s) cil managed
{
  .param [1]
  .custom instance void System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) 
  // 程式碼大小       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  call       instance void ConsoleApp2.MyStruct::Request()
  IL_0007:  nop
  IL_0008:  ret
} // end of method Program::Test
結語

struct-type 若是僅使用於 readonly field 和透過 in 參數修飾詞傳遞以及 readonly ref 之時,將它設計為 readonly struct 會比一般可變性的 strcut 來得有效能優勢。