您的位置:首页 > 其它

探索系列:深入辨析 ReadOnly,Const

2007-12-23 00:38 447 查看


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 Main(string[] args)

{

(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_000a

IL_000a: ldloc.0

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 Main(string[] args) cil managed

{

.entrypoint

.maxstack 8

IL_0000: nop

IL_0001: newobj instance void TestConcoleApp.Program::.ctor()

IL_000b: nop

IL_000c: ret

}

.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文件格式里面的元数据表和托管进程的内存布局。

后记补记:[/b]

今天,又把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关键字的文章画上了一个比较完整的句号吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: