[.NET C#] Readonly與Const

  • 711
  • 0
  • C#
  • 2016-01-27

會想這篇的原因是因為前兩天在開發上,寫了一個具有DateTime參數的function,後來想將其改為optional所以設了 = DateTime.MinValue;結果就得到了一個"default parameter value for xxx must be a compile-time constant"才開始認真查了一下今天這個主題的內容,何謂compile-time constant?為何DateTime.minValue明明看起來就像常數,確不能當作參數的預設值?

Readonly

  • 由宣告時或建構式時初始化 (可重覆)
  • 執行時期常數(可於執行時期給值)
  • 不限型別
  • 可以設定為靜態

Const

  • 僅能在宣告時初始化
  • 編譯時期常數
  • 僅能為數值、布林值、字串或 null
  • 不可添加靜態修飾詞 (實際上沒有必要)

語法:

在readonly的使用上並沒有型別限制,任何型別均可加上readonly修飾詞(modifier),當然也可以設為靜態,與一般變數的差別在於其唯讀屬性。

public readonly DateTime curTime = DateTime.Now; //可為任何頪型
        public readonly int readOnlyVariable = 72;//可以宣告時一併初始化
        public VariableTest(int a)
        {
            readOnlyVariable = a;//或者於建構式中初始化,二者可重覆
        }

在const就限制較多了,除了型別有限之外,亦必須要在宣告時即設定其初始值。

public const int constValue=66;

而在MSDN有段文字要特別注意:

由於編譯器會傳播常數,以您的程式庫編譯的其他程式碼必須經過重新編譯,才能看到變更。

這段話代表的意思是,常數的內文會直接被編譯進對映的組件(DLL)中,如果常數內容更換的話,所有的組件需要一併被重新編譯,這部份由後面範例證明。

我們在一個solution中開兩個project, 放置兩支程式碼分別如下:

ClassLibrary中加上一個ConstTest類別

public class ConstTest
    {
        public const int MagicCode = 14456;
        public readonly int MagicCode2 = 17788;
        public ConstTest(int magicCode2)
        {
            MagicCode2 = magicCode2;
        }
    }
}

於ConsoleApplication中的Program中,叫用ConstTest中的常數MagicCode與初始化readonly的MagicCode。

class Program

    {

        static void Main(string[] args)

        {

            ConstTest c = new ConstTest(13333);//於建構時丟入初始化readonly變數的內容

            Console.WriteLine("readonly的MagicCode2:"+c.MagicCode2);

            Console.WriteLine("Const的MagicCode:"+ConstTest.MagicCode); //使用常數時如同使用靜態變數一樣

        }

    }

其中ConstTest類別的MagicNumber欄位為常數,在programe使用時語法與靜態方式相同,無需先實例化物件。而MagicCode2為readonly,在宣告時即初始化,但仍在建構式中亦放入初始化的程式碼片段。

執行後,magicCode2是由叫用端放入建構式的13333,而MagicCode為常數的14456。接著我們先來看一下ConstTEST的IL程式碼。

設定為常數的MagicCode被編譯後,在IL中直接寫上我們所設定的14456(十六進位為00003878)

而設定為readonly的欄位MagicCode,則不會直接寫入其內容,而是設定其為initonly的欄位。

而回到program的main來看起IL程式碼,我們看一下下面這段Code。

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 程式碼大小       66 (0x42)
  .maxstack  2
  .locals init ([0] class [constTest]constTest.ConstTest c)
  IL_0000:  nop
  IL_0001:  ldc.i4     0x3415
  IL_0006:  newobj     instance void [constTest]constTest.ConstTest::.ctor(int32)
  IL_000b:  stloc.0
  IL_000c:  ldstr      bytearray (72 00 65 00 61 00 64 00 6F 00 6E 00 6C 00 79 00   // r.e.a.d.o.n.l.y.
                                  84 76 4D 00 61 00 67 00 69 00 63 00 43 00 6F 00   // .vM.a.g.i.c.C.o.
                                  64 00 65 00 32 00 3A 00 )                         // d.e.2.:.
  IL_0011:  ldloc.0
  IL_0012:  ldfld      int32 [constTest]constTest.ConstTest::MagicCode2
  IL_0017:  box        [mscorlib]System.Int32
  IL_001c:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0026:  nop
  IL_0027:  ldstr      bytearray (43 00 6F 00 6E 00 73 00 74 00 84 76 4D 00 61 00   // C.o.n.s.t..vM.a.
                                  67 00 69 00 63 00 43 00 6F 00 64 00 65 00 3A 00 ) // g.i.c.C.o.d.e.:.
  IL_002c:  ldc.i4     0x3878 //ConstTest的MagicCode內容直接被寫入到叫用端的IL中
  IL_0031:  box        [mscorlib]System.Int32
  IL_0036:  call       string [mscorlib]System.String::Concat(object,
                                                              object)
  IL_003b:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0040:  nop
  IL_0041:  ret
} // end of method Program::Main

在23這行,我們在ConstTEST類別組件中設的14456的十六進位0x3878直接被寫入叫用端的IL程式中,也就是MSDN提到的一點"由於編譯器會傳播常數,以您的程式庫編譯的其他程式碼必須經過重新編譯,才能看到變更。";由於內容直接被編譯進IL程式碼中,而不是用參考的方式來取得ConstTest,所以,一旦ConstTest中的常數MagicCode改變,program所在的dll也要一併編譯才會是更新後的常數內容。這一點在程式步署時要特別注意,以避免部份的組件容內沒有更新。

 

會想這篇的原因是因為前兩天在開發上,寫了一個具有DateTime參數的function,後來想將其改為optional所以設了 = DateTime.MinValue;結果就得到了一個"default parameter value for xxx must be a compile-time constant"才開始認真查了一下今天這個主題的內容,何謂compile-time constant?為何DateTime.minValue明明看起來就像常數,確不能當作參數的預設值?(如果由最前面開始看,應該以經知道原因了),不過原本遇到的這個問題,就把DateTime改成nullable的方式來處理掉了。

 

參考:

https://msdn.microsoft.com/zh-tw/library/acdd6hb7.aspx

https://msdn.microsoft.com/zh-tw/library/e6w8fe1b.aspx

http://blog.csdn.net/high_mount/article/details/6665573

http://www.dotblogs.com.tw/tiffany/archive/2011/07/25/32096.aspx