首页 > 学院 > 开发设计 > 正文

C#基本知识点-Readonly和Const的区别

2019-11-17 02:38:25
字体:
来源:转载
供稿:网友

C#基本知识点-Readonly和Const的区别

C#基本知识点-Readonly和Const的区别

什么是静态常量(Const)和动态常量(Readonly)

先解释下什么是静态常量(Const)以及什么是动态常量(Readonly)。

静态常量(Const)是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值。

动态常量(Readonly)的值则是在运行的那一刻才获得的,编译器编译期间将其标示为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化。

静态常量(Const)和动态常量(Readonly)之间的区别

静态常量(Compile-timeConstant)

动态常量(RuntimeConstant)

定义

声明的同时要设置常量值。

声明的时候可以不需要进行设置常量值,可以在类的构造函数中进行设置。

类型限制

只能修饰基元类型,枚举类型或者字符串类型。

没有限制,可以用它定义任何类型的常量。

对于类对象而言

对于所有类的对象而言,常量的值是一样的。

对于类的不同对象而言,常量的值可以是不一样的。

内存消耗

无。

要分配内存,保存常量实体。

综述

性能要略高,无内存开销,但是限制颇多,不灵活。

灵活,方便,但是性能略低,且有内存开销。

Const修饰的常量在声明的时候必须初始化;Readonly修饰的常量则可以延迟到构造函数初始化。

Const常量既可以声明在类中也可以在函数体内,但是StaticReadonly常量只能声明在类中。Const是静态常量,所以它本身就是Static的,因此不能手动再为Const增加一个Static修饰符。

Const修饰的常量在编译期间就被解析,即:经过编译器编译后,我们都在代码中引用Const变量的地方会用Const变量所对应的实际值来代替;Readonly修饰的常量则延迟到运行的时候。

举个例子来说明一下:

publicstaticreadonlyintNumberA=NumberB*10;

publicstaticreadonlyintNumberB=10;

publicconstintNumberC=NumberD*10;

publicconstintNumberD=10;

staticvoidMain(string[]args)

{

Console.WriteLine("NumberAis{0},NumberBis{1}.",NumberA,NumberB);//NumberAis0,NumberBis10.

Console.WriteLine("NumberCis{0},NumberDis{1}.",NumberC,NumberD);//NumberCis100,NumberDis10.

Console.ReadKey();

}

以上是语法方面的应用,那在实际的用法上,还是有些微妙的变化,通常不易发觉.

举个例子来说明一下:

在程序集DoTestConst.dll中有一个类MyClass,定义了一个公开的静态变量Count

publicstaticclassMyClass

{

publicconstintCount=10;

}

然后另外一个应用程序中引用DoTestConst.dll,并在代码中作如下调用:

publicstaticvoidMain(string[]args)

{

Console.WriteLine(DoTestConst.MyClass.Count);//输出10

Console.ReadKey();

}

毫无疑问,非常简单的代码,直接输出10。

接下来更新MyClass的Count的值为20,然后重新编译DoTestConst.dll,并更新到应用程序的所在目录中,注意不要编译应用程序。那么这时候的输出结果按预期那么想应该是20才对,但实际上还是10,为什么呢?

这就是Const的特别之处,有多特别还是直接看生成的IL,查看IL代码(假设这时候Count的值为10)

IL_0000:nop

IL_0001:ldc.i4.s10

IL_0003:callvoid[mscorlib]System.Console::WriteLine(int32)

红色代码很明显的表明了,直接加载10,没有通过任何类型的加载然后得到对应变量的,也就是说在运行时没有去加载DoTestConst.dll,那么是否意味着没有DoTestConst.dll也可以运行呢?答案是肯定的,删除DoTestConst.dll也可以运行,是否很诡异呢?也就解释了之前的实验,为什么更新Const变量的值之后没有调用新的值,因为程序在运行的时候根本不会去加载DoTestConst.dll。那么10这个值是从哪来的呢?实际上CLR对于Const变量做了特殊处理,是将Const的值直接嵌入在生成的IL代码中,在执行的时候不会再去从dll加载。这也带来了一个不容易发觉的Bug,因此在引用其他程序集的Const变量时,需考虑到版本更新问题,要解决这个问题就是把调用的应用程序再编译一次就ok了。但实际程序部署更新时可能只更新个别文件,这时候就必须用Readonly关键字来解决这个问题。

接下来看Readonly的版本:

publicstaticclassMyClass

{

publicstaticreadonlyintCount=10;

}

调用方代码不变,接着看生成的IL代码:

IL_0000:nop

IL_0001:ldsfldint32[DoTestConst]DoTestConst.MyClass::Count

IL_0006:callvoid[mscorlib]System.Console::WriteLine(int32)

很明显加载代码变了,一个很常见的ldsfld动作,请求了DoTestConst.MyClass的Count变量,是通过强制要求加载DoTestConst来实现的。因此这时候更新Count的值重新编译之后,还是不编译调用程序,然后再执行就会看到新的值。而这时候如果删除DoTestConst.dll那么,会出现找不到dll之类的异常。这也充分说明了对于Readonly定义的变量是在运行时加载的。

动态常量(Readonly)被赋值后不可以改变

ReadOnly变量是运行时变量,它在运行时第一次赋值后将不可以改变。其中“不可以改变”分为两层意思:

对于值类型变量,值本身不可以改变(Readonly,只读)

对于引用类型变量,引用本身(相当于指针)不可改变。

值类型变量,举个例子说明一下:

publicclassStudent

{

publicreadonlyintAge;

publicStudent(intage)

{

this.Age=age;

}

}

Student的实例Age在构造函数中被赋值以后就不可以改变,下面的代码不会编译通过:

Studentstudent=newStudent(20);

student.Age=21;//错误信息:无法对只读的字段赋值(构造函数或变量初始化器中除外)

引用类型变量,举个例子说明一下:

publicclassStudent

{

publicintAge;//注意这里的Age是没有readonly修饰符的

publicStudent(intage)

{

this.Age=age;

}

}

publicclassSchool

{

publicreadonlyStudentStudent;

publicSchool(Studentstudent)

{

this.Student=student;

}

}

School实例的Student是一个引用类型的变量,赋值后,变量不能再指向其他任何的Student实例,所以,下面的代码将不会编译通过:

Schoolschool=newSchool(newStudent(10));

school.Student=newStudent(20);//错误信息:无法对只读的字段赋值(构造函数或变量初始化器中除外)

引用本身不可以改变,但是引用说指向的实例的值是可以改变的。所以下面的代码是可以编译通过的:

Schoolschool=newSchool(newStudent(10));

school.Student.Age=20;

在构造方法中,我们可以多次对Readonly修饰的常量赋值。举个例子说明一下:

publicclassStudent

{

publicreadonlyintAge=20;//注意:初始化器实际上是构造方法的一部分,它其实是一个语法糖

publicStudent(intage)

{

this.Age=age;

this.Age=25;

this.Age=30;

}

}

总结

Const和Readonly的最大区别(除语法外)

Const的变量是嵌入在IL代码中,编译时就加载好,不依赖外部dll(这也是为什么不能在构造方法中赋值)。Const在程序集更新时容易产生版本不一致的情况。

Readonly的变量是在运行时加载,需请求加载dll,每次都获取最新的值。Readonly赋值引用类型以后,引用本身不可以改变,但是引用所指向的实例的值是可以改变的。在构造方法中,我们可以多次对Readonly赋值。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表