前言ide
不少.NET的初學者對const和readonly的使用很模糊,本文就const和readonly作一下深度分析,包括:性能
1. const數據類型的優點spa
2. const數據類型的劣勢3d
3. readonly類型的優點版本控制
4. readonly類型的劣勢code
5. 編譯器對const數據類型如何作處理對象
6. 編譯器對readonly數據類型如何作處理blog
const常量內存
編譯器在編譯const常量的時候會直接把常量值嵌入到IL代碼中,這是const常量的優點,即在程序運行過程當中不須要經過類型對象或者實例對象檢索字段的值,這樣多多少少能提升性能。爲了更直觀的看一下const常量在IL代碼下的存在狀態,咱們舉一個例子。ci
註釋:IL代碼是編譯器編譯完成生成的代碼,咱們所見到的託管dll文件和託管exe文件內部其實都是這種代碼。
const常量應用實例
1. 在VS中新建一個解決方案,命名爲ConstantTest,在解決方案中添加兩個項目,分別爲ClassTest(類庫項目)和ConstantTest(控制檯項目)
2. 在ClassTest項目中添加類ClassTemp,ClassTemp的具體代碼以下:
using System; namespace ClassTest { public class ClassTemp { public const Int32 num_X = 20; public const Int32 num_Y = 20; public const String rightInfo = "Calculate right!"; public const String errorInfo = "Calculate error!"; } }
3. 在項目ConstantTest項目中引用ClassTest.dll,在Program.cs中添加以下代碼:
using System; using ClassTest; namespace ConstantTest { class Program { static void Main(string[] args) { try { Int32 x = ClassTemp.num_X; Int32 y = ClassTemp.num_Y; Int32 result = x * y; Console.WriteLine(ClassTemp.rightInfo + " result: " + result); } catch (Exception) { Console.WriteLine(ClassTemp.errorInfo); } finally { Console.ReadKey(); } } } }
4. 而後編譯項目ConstantTest,用ildasm查看生成的ConstantTest.exe文件,結果以下:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 67 (0x43) .maxstack 2 .locals init ([0] int32 x, [1] int32 y, [2] int32 result) IL_0000: nop .try { .try { IL_0001: nop IL_0002: ldc.i4.s 10 IL_0004: stloc.0 IL_0005: ldc.i4.s 20 IL_0007: stloc.1 IL_0008: ldloc.0 IL_0009: ldloc.1 IL_000a: mul IL_000b: stloc.2 IL_000c: ldstr"Calculate right!result: "
IL_0011: ldloc.2 IL_0012: box [mscorlib]System.Int32 IL_0017: call string [mscorlib]System.String::Concat(object, object) IL_001c: call void [mscorlib]System.Console::WriteLine(string) IL_0021: nop IL_0022: nop IL_0023: leave.s IL_0035 } // end .try catch [mscorlib]System.Exception { IL_0025: pop IL_0026: nop IL_0027: ldstr"Calculate error!"
IL_002c: call void [mscorlib]System.Console::WriteLine(string) IL_0031: nop IL_0032: nop IL_0033: leave.s IL_0035 } // end handler IL_0035: nop IL_0036: leave.s IL_0041 } // end .try finally { IL_0038: nop IL_0039: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_003e: pop IL_003f: nop IL_0040: endfinally } // end handler IL_0041: nop IL_0042: ret } // end of method Program::Main
不用去理解上面IL代碼各行的意思,咱們只須要肯定咱們在代碼中使用的常量值在IL代碼中確實是以
10 20 Calculate right! Calculate error!
出現的,這也說明咱們開始作的結論是正確的。
const常量的問題
可是const常量的這種性質也帶來了缺陷,就是針對於版本控制出現的問題,就拿上面的列子來講,若是上面的兩個項目分別由兩個不一樣的人甚至公司來完成的(公司A和B),如今公司A要對ClassTemp.dll進行升級,好比把裏面的num_X改爲30,若是咱們把升級後的ClassTemp.dll放到ConstantTest的項目中,ConstantTest的執行結果是不會變的(仍是400),由於ConstantTest.exe的IL代碼以前已經肯定了,除非咱們重現編譯ConstantTest項目,若是對於項目特多的狀況這樣顯然不方便。
由於常量的值是由編譯器來識別的,因此常量的類型只能是基元類型,這是const常量的另外一個劣勢
readonly字段的優點
相比const常量來講,readonly字段在版本控制方面就行了不少,咱們仍是拿上面的列子來講,不過相應的數據得改一下,直接把修改後的代碼貼出來:
ClassTemp.cs
using System; namespace ClassTest { public class ClassTemp { public static readonly Int32 num_XR = 20; public static readonly Int32 num_YR = 20; public static readonly String rightInfoR = "Calculate right!"; public static readonly String errorInfoR = "Calculate error!"; } }
ConstantTest—>Program.cs
using System; using ClassTest; namespace ConstantTest { class Program { static void Main(string[] args) { try { Int32 x = ClassTemp.num_XR; Int32 y = ClassTemp.num_YR; Int32 result = x * y; Console.WriteLine(ClassTemp.rightInfoR + " result: " + result); } catch (Exception) { Console.WriteLine(ClassTemp.errorInfoR); } finally { Console.ReadKey(); } } } }
先看一下結果:
把num_XR改爲30,從新編譯ClassTemp項目,運行程序結果以下:
結果確實發生了改變,咱們下面說一下readonly這種字段是怎麼加載的,在說明以前,貼出IL代碼
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代碼大小 78 (0x4e) .maxstack 3 .locals init ([0] int32 x, [1] int32 y, [2] int32 result) IL_0000: nop .try { .try { IL_0001: nop IL_0002: ldsfld int32 [ClassTest]ClassTest.ClassTemp::num_XR IL_0007: stloc.0 IL_0008: ldsfld int32 [ClassTest]ClassTest.ClassTemp::num_YR IL_000d: stloc.1 IL_000e: ldloc.0 IL_000f: ldloc.1 IL_0010: mul IL_0011: stloc.2 IL_0012: ldsfld string [ClassTest]ClassTest.ClassTemp::rightInfoR IL_0017: ldstr " result: " IL_001c: ldloc.2 IL_001d: box [mscorlib]System.Int32 IL_0022: call string [mscorlib]System.String::Concat(object, object, object) IL_0027: call void [mscorlib]System.Console::WriteLine(string) IL_002c: nop IL_002d: nop IL_002e: leave.s IL_0040 } // end .try catch [mscorlib]System.Exception { IL_0030: pop IL_0031: nop IL_0032: ldsfld string [ClassTest]ClassTest.ClassTemp::errorInfoR IL_0037: call void [mscorlib]System.Console::WriteLine(string) IL_003c: nop IL_003d: nop IL_003e: leave.s IL_0040 } // end handler IL_0040: nop IL_0041: leave.s IL_004c } // end .try finally { IL_0043: nop IL_0044: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_0049: pop IL_004a: nop IL_004b: endfinally } // end handler IL_004c: nop IL_004d: ret } // end of method Program::Main
CLR加載字段
CLR支持靜態字段和非靜態字段,咱們上面使用的靜態字段,靜態字段是在類型對象中加載的,類型對象是在類型加載到一個AppDomain時建立的(AppDomain能夠暫時理解爲程序運行的內存空間),而類型第一次有方法被調用的時候(包含構造方法)會加載到AppDomain中,咱們畫一個圖可能看的更清晰一些:
這樣每次啓動程序的時候ClassTemp類中的字段值都會從新賦值,因此即便不從新編譯ConstantTest項目,依然能獲得正確結果。
另外,readonly字段能夠是任何類型而不拘泥於基元類型。
注意:readonly字段只能在一個構造方法中寫入,另外,利用反射頁能夠修改字段的值。
若是您有什麼意見或者文章對您有任何的幫助,請在評論區告訴我!!!