會想這篇的原因是因為前兩天在開發上,寫了一個具有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