← 筆記

C#筆記 – 字段

字段
  •   readonly int someReadonly = 2;
      static int someStaticField = 3;
  •   //Check in ildasm
      someReadonly : private initonly int32
      someStaticField : private static int32
  • 字段是一個值類型的實例/引用類型的引用,一般聲明為私有

  • CLR支持類型(靜態)字段和實例(非靜態)字段

    • 靜態時,為類型狀態的一部分;非靜態時,為對象狀態的一部分
    • 靜態字段的動態內存
      • 類型是在引用了它的任何方法首次進行JIT編譯時,加載到一個AppDomain裡的
      • 類型加載到AppDomain後,創建類型對象
      • 容納靜態字段數據所需的動態內存是在類型對象中分配的
        • 而字段的值則在運行時才能獲取
    • 實例字段的動態內存
      • 在構造類型實例時分配
字段修飾符
  • CLRC#Desc
    Staticstatic類型狀態一部分
    Instance默認與類型的一個實例關聯
    InitOnlyreadonly只能由一個構造器中的代碼寫入
    Volatilevolatilecompiler、CLR、硬件不會對訪問這種字段的代碼執行「線程不安全」的優化措施
  • CLR支持readonly和read/write字段

    • readonly只能在構造時初始化

    • 編譯器和驗證機制會確保readonly字段不會被構造器以外的任何方法寫入

      • 可以利用反射來修改readonly字段
    • 如果readonly的字段是引用類型,其不可改變之處在於其引用,而不是它引用的對象,以readonly數組為例

      •   public sealed class AType
          {
              public static readonly char[] charArr = new char[] { 'A', 'B', 'C' };
          }
          
          public sealed class BType
          {
              public static void Modify()
             {
                  //有效且編譯通過
                  AType.charArr[0] = 'X';
                  AType.charArr[1] = 'Y';
                  AType.charArr[2] = 'Z';
          
                  //無效且無法通過編譯
                  //AType.charArr = null;
             }
          }
  • public static readonly VS const

    • 由於字段只有在運行時才能獲取,因此引用了定義字段的exe在編譯時,並不會像常量一樣,把字段的值嵌入到IL裡面
      • 所以,exe調用字段時,需要加載定義字段的dll,並獲取對應的字段值

      • 這使得只要重新打包dll,就可以修改exe加載的字段值;而不需要像const一樣,還要重新打包exe

      • 首次打包DLL和EXE

        •   //DLL
            public sealed class SomeLibraryType
            {
                public static readonly string ReadOnlyString = "Orange";
            }
        •   //EXE
            public sealed class Program
            {
                public static void Main()
                {
                    Console.WriteLine("Readonly String: " +
                                      SomeLibraryType.ReadOnlyString);
                    Console.ReadLine();
                }
            }
        •   IL_001d:  nop
            IL_001e:  ldstr      "Readonly String: "
            IL_0023:  ldsfld     string [Program]CLR_Ch7.SomeLibraryType::ReadOnlyString //加載定義了字段的程序集
            IL_0028:  call       string [mscorlib]System.String::Concat(string,
            string)
            IL_002d:  call       void [mscorlib]System.Console::WriteLine(string)
            IL_0032:  nop
            IL_0033:  call       string [mscorlib]System.Console::ReadLine()
            IL_0038:  pop
        • 輸出”Orange”

      • 修改字段的值後,只重新打包DLL

        •   //DLL
            public sealed class SomeLibraryType
            {
                public static readonly string ReadOnlyString = "Apple";
            }
      • 同時readonly又確保了字段的不變性,因此,public static readonly才多被用作const的「彈性替代品」

內聯初始化字段
  • 字段除可以在構造器裡面初始化外,還可以像下面這樣進行內聯初始化

    •   public sealed class Program
        {
            //內聯初始化
            public readonly int inLineVar = 100;
            
            //構造器初始化
            public readonly int constructorVar;
            public Program()
            {
                constructorVar = 500;
            }
        }
  • 但內聯初始化在C#底層也是在構造器對字段進行初始化的,內聯初始化只是一種語法糖

  • 另外,使用內聯語法還有一些性能問題

    • 在引用類型中:
      • C#

        •   internal sealed class SomeType
            {
                int m_X = 5;
                string m_S = "Test";
                byte m_B;
            
                public SomeType() { }
                public SomeType(int x) { m_X = x; }
                public SomeType(string s) { m_S = s; }
            }
      • IL

        • SomeType()

          •   .method public hidebysig specialname rtspecialname 
                      instance void  .ctor() cil managed
              {
                // 程式碼大小       27 (0x1b)
                .maxstack  8
                IL_0000:  ldarg.0
                IL_0001:  ldc.i4.5
                IL_0002:  stfld      int32 CLR_Ch8.SomeType::m_X
                IL_0007:  ldarg.0
                IL_0008:  ldstr      "Test"
                IL_000d:  stfld      string CLR_Ch8.SomeType::m_S
                IL_0012:  ldarg.0
                IL_0013:  call       instance void [System.Runtime]System.Object::.ctor()
                IL_0018:  nop
                IL_0019:  nop
                IL_001a:  ret
              } // end of method SomeType::.ctor
        • SomeType(int x)

          •   .method public hidebysig specialname rtspecialname 
                      instance void  .ctor(int32 x) cil managed
              {
                // 程式碼大小       34 (0x22)
                .maxstack  8
                IL_0000:  ldarg.0
                IL_0001:  ldc.i4.5
                IL_0002:  stfld      int32 CLR_Ch8.SomeType::m_X
                IL_0007:  ldarg.0
                IL_0008:  ldstr      "Test"
                IL_000d:  stfld      string CLR_Ch8.SomeType::m_S
                IL_0012:  ldarg.0
                IL_0013:  call       instance void [System.Runtime]System.Object::.ctor()
                IL_0018:  nop
                IL_0019:  nop
                IL_001a:  ldarg.0
                IL_001b:  ldarg.1
                IL_001c:  stfld      int32 CLR_Ch8.SomeType::m_X
                IL_0021:  ret
              } // end of method SomeType::.ctor
        • SomeType(string s)

          •   .method public hidebysig specialname rtspecialname 
                      instance void  .ctor(string s) cil managed
              {
                // 程式碼大小       34 (0x22)
                .maxstack  8
                IL_0000:  ldarg.0
                IL_0001:  ldc.i4.5
                IL_0002:  stfld      int32 CLR_Ch8.SomeType::m_X
                IL_0007:  ldarg.0
                IL_0008:  ldstr      "Test"
                IL_000d:  stfld      string CLR_Ch8.SomeType::m_S
                IL_0012:  ldarg.0
                IL_0013:  call       instance void [System.Runtime]System.Object::.ctor()
                IL_0018:  nop
                IL_0019:  nop
                IL_001a:  ldarg.0
                IL_001b:  ldarg.1
                IL_001c:  stfld      string CLR_Ch8.SomeType::m_S
                IL_0021:  ret
              } // end of method SomeType::.ctor
        • 簡單來說,使用「內聯」語化來初始化字段,C#編譯器會將這些字段轉換成構造器中的代碼來進行初始化

          • 而這種轉換會在定義的「所有」構造器中發生,因此,在上例中有3個構造器的話,每一個構造器都會生成「內聯初始化字段轉換至構造器內初始化」的IL代碼
          • 而在完成這些初始化後,會插入對基類實例構造器的調用
          • 最後才是自身的實例構造器的代碼
      • 一種優化的手段是創建一個構造器來執行所有字段的初始化,然後讓其他構造器顯式調用(使用this關鍵字)該構造器,從而減少生成的代碼:

        • C#

          •   internal sealed class SomeTypeOptimized
              {
                  int m_x;
                  string m_s;
                  byte m_b;
              
                  public SomeTypeOptimized()
                  {
                      m_x = 10;
                      m_s = "Test";
                      m_b = 0;
                  }
              
                  public SomeTypeOptimized(int x) : this()
                  {
                      m_x = x;
                  }
              
                  public SomeTypeOptimized(string s) : this()
                  {
                      m_s = s;
                  }
              
                  public SomeTypeOptimized(int x, string s) : this()
                  {
                      m_x = x;
                      m_s = s;
                  }
              }
        • IL

          • SomeTypeOptimized()

            •   .method public hidebysig specialname rtspecialname 
                        instance void  .ctor() cil managed
                {
                  // 程式碼大小       35 (0x23)
                  .maxstack  8
                  IL_0000:  ldarg.0
                  IL_0001:  call       instance void [System.Runtime]System.Object::.ctor()
                  IL_0006:  nop
                  IL_0007:  nop
                  IL_0008:  ldarg.0
                  IL_0009:  ldc.i4.s   10
                  IL_000b:  stfld      int32 CLR_Ch8.SomeTypeOptimized::m_x
                  IL_0010:  ldarg.0
                  IL_0011:  ldstr      "Test"
                  IL_0016:  stfld      string CLR_Ch8.SomeTypeOptimized::m_s
                  IL_001b:  ldarg.0
                  IL_001c:  ldc.i4.0
                  IL_001d:  stfld      uint8 CLR_Ch8.SomeTypeOptimized::m_b
                  IL_0022:  ret
                } // end of method SomeTypeOptimized::.ctor
          • SomeTypeOptimized(int x) : this()

            •   .method public hidebysig specialname rtspecialname 
                        instance void  .ctor(int32 x) cil managed
                {
                  // 程式碼大小       16 (0x10)
                  .maxstack  8
                  IL_0000:  ldarg.0
                  IL_0001:  call       instance void CLR_Ch8.SomeTypeOptimized::.ctor()
                  IL_0006:  nop
                  IL_0007:  nop
                  IL_0008:  ldarg.0
                  IL_0009:  ldarg.1
                  IL_000a:  stfld      int32 CLR_Ch8.SomeTypeOptimized::m_x
                  IL_000f:  ret
                } // end of method SomeTypeOptimized::.ctor
          • SomeTypeOptimized(string s) : this()

            •   .method public hidebysig specialname rtspecialname 
                        instance void  .ctor(string s) cil managed
                {
                  // 程式碼大小       16 (0x10)
                  .maxstack  8
                  IL_0000:  ldarg.0
                  IL_0001:  call       instance void CLR_Ch8.SomeTypeOptimized::.ctor()
                  IL_0006:  nop
                  IL_0007:  nop
                  IL_0008:  ldarg.0
                  IL_0009:  ldarg.1
                  IL_000a:  stfld      string CLR_Ch8.SomeTypeOptimized::m_s
                  IL_000f:  ret
                } // end of method SomeTypeOptimized::.ctor
          • SomeTypeOptimized(int x, string s) : this()

            •   .method public hidebysig specialname rtspecialname 
                        instance void  .ctor(int32 x,
                                             string s) cil managed
                {
                  // 程式碼大小       23 (0x17)
                  .maxstack  8
                  IL_0000:  ldarg.0
                  IL_0001:  call       instance void CLR_Ch8.SomeTypeOptimized::.ctor()
                  IL_0006:  nop
                  IL_0007:  nop
                  IL_0008:  ldarg.0
                  IL_0009:  ldarg.1
                  IL_000a:  stfld      int32 CLR_Ch8.SomeTypeOptimized::m_x
                  IL_000f:  ldarg.0
                  IL_0010:  ldarg.2
                  IL_0011:  stfld      string CLR_Ch8.SomeTypeOptimized::m_s
                  IL_0016:  ret
                } // end of method SomeTypeOptimized::.ctor

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter