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";
          }
        • 輸出”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