字段
readonly int someReadonly = 2;
static int someStaticField = 3;//Check in ildasm
someReadonly : private initonly int32
someStaticField : private static int32字段是一個值類型的實例/引用類型的引用,一般聲明為私有
CLR支持類型(靜態)字段和實例(非靜態)字段
- 靜態時,為類型狀態的一部分;非靜態時,為對象狀態的一部分
靜態字段的動態內存
- 類型是在引用了它的任何方法首次進行JIT編譯時,加載到一個AppDomain裡的
類型加載到AppDomain後,創建類型對象
容納靜態字段數據所需的動態內存是在類型對象中分配的
而字段的值則在運行時才能獲取
實例字段的動態內存
在構造類型實例時分配
字段修飾符
CLR C# Desc Static static 類型狀態一部分 Instance 默認 與類型的一個實例關聯 InitOnly readonly 只能由一個構造器中的代碼寫入 Volatile volatile compiler、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