2012-04-29 00:10:57|?次阅读|上传:wustguangh【已有?条评论】发表评论
在C#程序中,定义常量主要有两种方式,即:使用const修饰符或者使用readonly修饰符,但这两者之间究竟有什么区别呢?也许很多人平时没有注意到,然而在某些场合因为不理解两者之间的区别造成应用程序无法按照设计的方案执行,是很难查找其原因的,所以理解它们的基本原理,对于开发和维护都是非常有必要。在类库开发定义常量的时候,个人偏向于使用static readonly组合关键字,本文将会给出具体的原因说明。
一个简单的应用程序解决方案,如下:
其中,类库CSharpLib里定义一个简单的类SomeType如下:
namespace CSharpLib { public class SomeType { public const int ConstField = 50; public static readonly int ReadonlyField = 50; } }
在控制台应用程序ConsoleApp中,引用类库CSharpLib,然后写下如下代码:
using System; namespace ConsoleApp { using CSharpLib; class Program { static void Main(string[] args) { Console.WriteLine("Const field is {0}.", SomeType.ConstField); Console.WriteLine("Readonly field is {0}.", SomeType.ReadonlyField); Console.ReadKey(); } } }
这样这个控制台应用程序的输出就都是50,这个结果应该是每个开发人员都预期的,没有任何可疑之处。
当我们把类库CSharpLib中的常量都改变时:
namespace CSharpLib { public class SomeType { public const int ConstField = 55; public static readonly int ReadonlyField = 5555; } }
也就是说,我们预期的目标输出是55和5555。当改好类库,开发人员发现控制台应用程序没做出任何修改,所以只将类库重新生成,然后将.dll文件拷到 控制台应用程序bin目录下(做过asp.net开发的应该都知道,当某个类库修改时,我们通常都直接拷贝修改后的类库.dll到网站下的bin目录,除 了习惯性偷懒,版本控制也是一个问题),然后双击运行ConsoleApp.exe文件,我们惊讶地发现当前的输出不是55和5555,而是50和 5555,也就是说,const字段修改后没有达到预期目的。
为什么会发生上面的现象呢?从c#的常量的本质说起。
(1)、什么是常量(书中原话摘录)
“常量(constant)是一个特殊的符号,它是一个从不变化的值。定义常量符号时,它的值必须能在编译时确定。确定之后,编译器将常量的值保存到程序集的元数据中。常量总是被视为静态成员,而不是实例成员。代码引用一个常量符号时,编译器会在定义常量的程序集的元数据中查找该符号,提取常量的值,并将值嵌入生成的IL代码中。由于常量的值直接嵌入代码,所以在运行时,不需要为常量分配内存。除此之外,不能获取常量的地址,也不能以传引用的方式传递常量。这些限制同时意味着,常量没有很好的跨程序集版本控制特性。“
(2)、静态常量和动态常量
简单来说,用const修饰的就是静态常量(compile-time constants),而且它是隐式静态的。动态常量(runtime constants)的值是在运行时获得的,IL中将其标为只读常量,而不是用常量的值代替。
下面对二者的特点进行简单比较:
静态常量 |
动态常量 |
|
内存分配 |
无 |
有 |
可使用类型 |
较少的C#基元类型,如String,Int32等等 |
任意类型 |
初始化赋值 | 声明变量时同时赋值 | 可在构造函数中赋值 |
何时发挥作用 | 编译时进行替换,可生成元数据 | 相当于类中的数据成员 |
通过上述分析,我们可以理解1中所产生的输出不一致现象,是因为const字段ConstField已经嵌入到控制台应用程序的IL代码中(获取ConstField字段并不从类库的dll文件中加载),从IL中我们可以看得很清楚:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 47 (0x2f) .maxstack 8 IL_0000: nop IL_0001: ldstr "Const field is {0}." IL_0006: ldc.i4.s 50 //<span style="color: #ff0000;">const字段没有变化</span> IL_0008: box [mscorlib]System.Int32 IL_000d: call void [mscorlib]System.Console::WriteLine(string, object) IL_0012: nop IL_0013: ldstr "Readonly field is {0}." IL_0018: ldsfld int32 [CSharpLib]CSharpLib.SomeType::ReadonlyField IL_001d: box [mscorlib]System.Int32 IL_0022: call void [mscorlib]System.Console::WriteLine(string, object) IL_0027: nop IL_0028: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() IL_002d: pop IL_002e: ret } // end of method Program::Main
而不是像static readonly字段一样从内存加载类库程序集。如果应用程序需获取新值,也必须重新编译,或者使用readonly字段。
我记得以前看过的一篇博文,是讨论枚举使用中输出不一致情况的,google了一下。您可以参考这一篇小心枚举陷阱进行比较,如果您对IL有了解,可以简单从IL着手分析对比,更能加深理解。
本文转自:http://www.cnblogs.com/jeffwongishandsome/archive/2010/11/01/1866642.html