探索系列:深入辨析 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…
过后,特意研究了下这两个关键字究竟有何不同:
首先,从CLI中对CTS的Type的规定说起。在第四版本的ECMA-355的CLI标准中,规定的对于每一个Type,都可以包含很多成员。所有的成员(members),一共可以有三种:第一种叫做fields,它是定义的和这个类型相关的存储单元。第二中成员类型就是我们熟悉的方法(Method)。第三中,叫做nested type。也就是嵌套类型,对于每一个type里面,都可以包含对于别的type的声明和使用。
对于所有的成员,又可以分为两种:per-type member,per-instance member。顾名思义,前一种,是对于每一个type只有一个的成员,譬如static修饰的field。后一种,是对于每一个运行时候的实例,都保持一个不同的实现版本。
对于其他的类型的成员,包括我们熟知的Properties,Events之类,这些反编译成为IL语言之后可以看到,全部直接的,或者是间接的转换成为了方法。
有的时候,我们需要一个field在它的整个生命周期里面,它所包含的这个值是不改变的。这个时候,根据这个变量被赋值的不同的情况,CLR为提供了两种技术来实现这种要求:
第一种,field里面包含的值,可以在编译的时候被计算出来。就是const关键字声明的变量。这种方法的实现效率是最高的。这个值,是作为一个字面上的固定的值,保存在包含这个type的module文件的metadata里面。Const定义的这个值,可以是一个表达式,但是这个表达式的结果,必须是编译的时候可以计算出来的。这样,在编译的时候,就可以植入到其余的指令里面去。
Const定义的field,必须在定义的时候,就给初始化了,而且,一旦初始化了之后,就不能再改变它的值。同时,不能声明一个const的field为static,因为,const定义的field就表明这个field是一个static的。
任何对const定义的常量的修改,都会抛出一个编译时错误。
我们可以把const修饰的field,归类到member的per-type member里面去。
第二种,是被readonly修饰的field。Readonly修饰的常量,可能是因为,有的时候,我们需要一些field,它的值是不应该改变的,但是,在运行之前,是不知道这个值的,这个时候,就可以用readonly来修饰这个field。
这个readonly修饰的field,就为常量这个名词提供了另外一种灵活得多的解决方案了。首先,它的值,是可以在运行的时候,根据别的变量动态计算出来的,而const修饰的常量的值就不能这样了。同时,不同于const,它是一个per-instance member。也就是说,对于每一个实例,它都可以保存一个不同的实现版本,而const就不行了。对于某一个type的所有的实现实例,const定义的field,只能有一个不变的值。
写到这里,从原理的角度已经阐述的比较清楚了,接着用一个实例来分析下:
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之后,得到下面的显示结果:
.namespace TestConcoleApp
{
.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
//这里可以看到,一个const定义的一个field,是literal,并且被static关键字来修饰。同时,在编译的时候,就计算得到了这个field的值。
.field public static literal int32 conField = int32(136518)
//readonly关键字修饰的field,只是被用initonly来修饰,per-instance类型。
.field public initonly int32 roField
.field private int32 _property
//在程序的属性里面定义的get关键字,最终在这里被转换成为了一个方法来处理。
.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动作,最终在这里转化成为了一个方法
.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来表示一个属性,实际上是转换成为了两个方法来操作一个private修饰的field
.property instance int32 Property()
{
.get instance int32 TestConcoleApp.Program::get_Property()
.set instance void TestConcoleApp.Program::set_Property(int32)
}
}
}
再看了readonly和const被编译成为IL的代码之后,对这两个关键字到底是如何运作,有什么区别和相同的地方,又有了进一步的确认和了解。
后记:
其实,我还想验证一下,public const int conField=122*1119;这一句中122*1119的结果,在作为一个module的时候,是计算好了保存在一个扩展了的DotNet下的PE文件的Metadata里面,然后在验证一下,运行的时候,一个type有多个实例,而const定义的field只有一个的。
不过动用了一大批调试工具和托管模块结构查看工具,把PE格式文件里面的元数据表狠狠的都刨了一遍,也没找到这个值。在这个文件执行的时候,windbg attach到这个进程,把这个模块在的内存刨了一个遍,还是没找到这个常量具体的location….郁闷坏了
先就整理到这里吧,改天好好的分析下PE文件格式里面的元数据表和托管进程的内存布局。
后记补记:
今天,又把metadata的几种表结构的reference看了看,把上面生成的那个托管模块里里外外又刨了一次,用dumpbin+ildasm,在查看元数据表的时候,终于找到了上面用readonly和const定义的两个字段在元数据里面的表示:
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已经保存到了托管PE文件的元数据表里面。并且,还有static,literal,hasdefault这几个关键字来修饰。而readonly修饰的field,只有一个initonly来修饰。
到这里,终于给这段刨根问底readonly和const关键字的文章画上了一个比较完整的句号吧。
posted on 2007-12-23 00:38 lbq1221119 阅读(1236) 评论(5) 编辑 收藏 举报