const與readonly深度分析(.NET)

前言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();
            }
        }
    }
}

先看一下結果:

image

把num_XR改爲30,從新編譯ClassTemp項目,運行程序結果以下:

image

結果確實發生了改變,咱們下面說一下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中,咱們畫一個圖可能看的更清晰一些:

image

這樣每次啓動程序的時候ClassTemp類中的字段值都會從新賦值,因此即便不從新編譯ConstantTest項目,依然能獲得正確結果。

另外,readonly字段能夠是任何類型而不拘泥於基元類型。

注意:readonly字段只能在一個構造方法中寫入,另外,利用反射頁能夠修改字段的值。

 

若是您有什麼意見或者文章對您有任何的幫助,請在評論區告訴我!!!

相關文章
相關標籤/搜索