探索系列:深入辨析 ReadOnly,Const
Last time ,when I was asked what is the diff between Readonly and Const,I was really ashamed that I said don’t know on explaining the exact difference between this two simple key word…
過后,特意研究了下這兩個關(guān)鍵字究竟有何不同:
首先,從CLI中對CTS的Type的規(guī)定說起。在第四版本的ECMA-355的CLI標(biāo)準(zhǔn)中,規(guī)定的對于每一個Type,都可以包含很多成員。所有的成員(members),一共可以有三種:第一種叫做fields,它是定義的和這個類型相關(guān)的存儲單元。第二中成員類型就是我們熟悉的方法(Method)。第三中,叫做nested type。也就是嵌套類型,對于每一個type里面,都可以包含對于別的type的聲明和使用。
對于所有的成員,又可以分為兩種:per-type member,per-instance member。顧名思義,前一種,是對于每一個type只有一個的成員,譬如static修飾的field。后一種,是對于每一個運行時候的實例,都保持一個不同的實現(xiàn)版本。
對于其他的類型的成員,包括我們熟知的Properties,Events之類,這些反編譯成為IL語言之后可以看到,全部直接的,或者是間接的轉(zhuǎn)換成為了方法。
有的時候,我們需要一個field在它的整個生命周期里面,它所包含的這個值是不改變的。這個時候,根據(jù)這個變量被賦值的不同的情況,CLR為提供了兩種技術(shù)來實現(xiàn)這種要求:
第一種,field里面包含的值,可以在編譯的時候被計算出來。就是const關(guān)鍵字聲明的變量。這種方法的實現(xiàn)效率是最高的。這個值,是作為一個字面上的固定的值,保存在包含這個type的module文件的metadata里面。Const定義的這個值,可以是一個表達式,但是這個表達式的結(jié)果,必須是編譯的時候可以計算出來的。這樣,在編譯的時候,就可以植入到其余的指令里面去。
Const定義的field,必須在定義的時候,就給初始化了,而且,一旦初始化了之后,就不能再改變它的值。同時,不能聲明一個const的field為static,因為,const定義的field就表明這個field是一個static的。
任何對const定義的常量的修改,都會拋出一個編譯時錯誤。
我們可以把const修飾的field,歸類到member的per-type member里面去。
第二種,是被readonly修飾的field。Readonly修飾的常量,可能是因為,有的時候,我們需要一些field,它的值是不應(yīng)該改變的,但是,在運行之前,是不知道這個值的,這個時候,就可以用readonly來修飾這個field。
這個readonly修飾的field,就為常量這個名詞提供了另外一種靈活得多的解決方案了。首先,它的值,是可以在運行的時候,根據(jù)別的變量動態(tài)計算出來的,而const修飾的常量的值就不能這樣了。同時,不同于const,它是一個per-instance member。也就是說,對于每一個實例,它都可以保存一個不同的實現(xiàn)版本,而const就不行了。對于某一個type的所有的實現(xiàn)實例,const定義的field,只能有一個不變的值。
寫到這里,從原理的角度已經(jīng)闡述的比較清楚了,接著用一個實例來分析下:
class Program
{
public const int conField=122*1119;
public readonly int roField;
private int _property;
public int Property
{
get
{
return _property;
}
set
{
_property = value;
}
}
static void
{
(new Program()).Method();
}
}
Diassemble之后,得到下面的顯示結(jié)果:
.namespace TestConcoleApp
{
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
//這里可以看到,一個const定義的一個field,是literal,并且被static關(guān)鍵字來修飾。同時,在編譯的時候,就計算得到了這個field的值。
.field public static literal int32 conField = int32(136518)
//readonly關(guān)鍵字修飾的field,只是被用initonly來修飾,per-instance類型。
.field public initonly int32 roField
.field private int32 _property
//在程序的屬性里面定義的get關(guān)鍵字,最終在這里被轉(zhuǎn)換成為了一個方法來處理。
.method public hidebysig specialname instance int32 get_Property() cil managed
{
.maxstack 1
.locals init (int32)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 TestConcoleApp.Program::_property
IL_0007: stloc.0
IL_0008: br.s IL_
IL_
IL_000b: ret
}
//對上面的一個Property的set動作,最終在這里轉(zhuǎn)化成為了一個方法
.method public hidebysig specialname instance void set_Property(int32 'value') cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.1
IL_0003: stfld int32 TestConcoleApp.Program::_property
IL_0008: ret
}
.method private hidebysig static void
{
.entrypoint
.maxstack 8
IL_0000: nop
IL_0001: newobj instance void TestConcoleApp.Program::.ctor()
IL_000b: nop
IL_
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
}
//這里用property來表示一個屬性,實際上是轉(zhuǎn)換成為了兩個方法來操作一個private修飾的field
.property instance int32 Property()
{
.get instance int32 TestConcoleApp.Program::get_Property()
.set instance void TestConcoleApp.Program::set_Property(int32)
}
}
}
再看了readonly和const被編譯成為IL的代碼之后,對這兩個關(guān)鍵字到底是如何運作,有什么區(qū)別和相同的地方,又有了進一步的確認和了解。
后記:
其實,我還想驗證一下,public const int conField=122*1119;這一句中122*1119的結(jié)果,在作為一個module的時候,是計算好了保存在一個擴展了的DotNet下的PE文件的Metadata里面,然后在驗證一下,運行的時候,一個type有多個實例,而const定義的field只有一個的。
不過動用了一大批調(diào)試工具和托管模塊結(jié)構(gòu)查看工具,把PE格式文件里面的元數(shù)據(jù)表狠狠的都刨了一遍,也沒找到這個值。在這個文件執(zhí)行的時候,windbg attach到這個進程,把這個模塊在的內(nèi)存刨了一個遍,還是沒找到這個常量具體的location….郁悶壞了
先就整理到這里吧,改天好好的分析下PE文件格式里面的元數(shù)據(jù)表和托管進程的內(nèi)存布局。
后記補記:
今天,又把metadata的幾種表結(jié)構(gòu)的reference看了看,把上面生成的那個托管模塊里里外外又刨了一次,用dumpbin+ildasm,在查看元數(shù)據(jù)表的時候,終于找到了上面用readonly和const定義的兩個字段在元數(shù)據(jù)里面的表示:
Field #1 (04000001)
-------------------------------------------------------
Field Name: conField (04000001)
Flags : [Public] [Static] [Literal] [HasDefault] (00008056)
DefltValue: (I4) 136518
CallCnvntn: [FIELD]
Field type: I4
Field #2 (04000002)
-------------------------------------------------------
Field Name: roField (04000002)
Flags : [Public] [InitOnly] (00000026)
CallCnvntn: [FIELD]
Field type: I4
可以看到,在編譯成為托管模塊的時候,這個const定義的變量就被計算了出來,把值136518已經(jīng)保存到了托管PE文件的元數(shù)據(jù)表里面。并且,還有static,literal,hasdefault這幾個關(guān)鍵字來修飾。而readonly修飾的field,只有一個initonly來修飾。
到這里,終于給這段刨根問底readonly和const關(guān)鍵字的文章畫上了一個比較完整的句號吧。
posted on 2007-12-23 00:38 lbq1221119 閱讀(1244) 評論(5) 收藏 舉報
浙公網(wǎng)安備 33010602011771號