透过IL看C# (外一篇) 警惕常量陷阱

透过IL看C# (外一篇)


警惕常量陷阱

 

原文地址:http://www.cnblogs.com/AndersLiu/archive/2008/11/23/csharp-via-il-constant-a.html

原创:Anders Liu

摘要:常量的含义本是“永远不会变的量”,但是如果作为类库开发人员,把常量用作“可以由我变,但不能由你变”的量,那就可能铸成大错了。

下面是老刘写的一个类库中的一个类:

代码1 - 老刘的“类库”

  1. namespace AndersLiu.CSharpViaIL.Constant_Library   
  2. {   
  3.     public class Library   
  4.     {   
  5.         public const int Version = 1;   
  6.            
  7.         public string GetVersion()   
  8.         {   
  9.             return string.Format("You are currently using version {0}.", Version);   
  10.         }   
  11.     }   
  12. }  
namespace AndersLiu.CSharpViaIL.Constant_Library { public class Library { public const int Version = 1; public string GetVersion() { return string.Format("You are currently using version {0}.", Version); } } }

其中的常量Version表示类库的当前版本,而方法GetVersion会给出描述当前版本的字符串。用下面的命令行可以将其编译为一个DLL:

csc /t:library Library.cs

接下来,一个倒霉的家伙从老刘这买了这个类库,写了自己的程序:

代码2 - 老刘的“消费者”

  1. namespace AndersLiu.CSharpViaIL.Constant_Program   
  2. {   
  3.     using System;   
  4.     using AndersLiu.CSharpViaIL.Constant_Library;   
  5.        
  6.     class Program   
  7.     {   
  8.         static void Main()   
  9.         {   
  10.             Console.WriteLine("Library Version: {0}", Library.Version);   
  11.                
  12.             Library lib = new Library();   
  13.             string verstr = lib.GetVersion();   
  14.             Console.WriteLine(verstr);   
  15.         }   
  16.     }   
  17. }  
namespace AndersLiu.CSharpViaIL.Constant_Program { using System; using AndersLiu.CSharpViaIL.Constant_Library; class Program { static void Main() { Console.WriteLine("Library Version: {0}", Library.Version); Library lib = new Library(); string verstr = lib.GetVersion(); Console.WriteLine(verstr); } } }

这个家伙用下面的命令行编译了自己的程序:

csc /r:Library.dll Program.cs

运行程序,一切正常,屏幕显示出预期的结果:

Library Version: 1
You are currently using version 1.

过了两天,老刘升级代码了。都升级哪些部分了呢?把Version的值改成2了,然后重新编译了Library。所以代码就不贴了。前面这个家伙因为很乖,从老刘这里买了正版,所以老刘承诺免费升级50次,因此他拿到了新版的Library.dll。

当这个家伙再跑自己的程序(注意他没有重新编译自己的代码),问题来了:

Library Version: 1
You are currently using version 2.

我们看到,通过常量访问得到的版本依然是1,而通过类库方法得到的版本字符串是2。

这是怎么回事呢?让我们祭出ILDasm,看一下Version常量的定义:

代码3 - IL中的常量定义

  1. .field public static literal int32 Version = int32(0x00000002)  
.field public static literal int32 Version = int32(0x00000002)

Version定义中的关键字literal表明,这是一个字面常量值。“字面”意味着其值将被直接编译到IL代码中,而不会保留对这个常量的引用。

因此,当我们看到Program.cs中Main方法的IL代码后,就不那么吃惊了:

代码4 - 引用Version常量部分的IL代码

  1. IL_0001:  ldstr      "Library Version: {0}"  
  2. IL_0006:  ldc.i4.1   
  3. IL_0007:  box        [mscorlib]System.Int32   
  4. IL_000c:  call       void [mscorlib]System.Console::WriteLine(string,   
  5.                                                               object)  
IL_0001: ldstr "Library Version: {0}" IL_0006: ldc.i4.1 IL_0007: box [mscorlib]System.Int32 IL_000c: call void [mscorlib]System.Console::WriteLine(string, object)

我们可以看到,这里根本没有Version这个常量的影子。只有IL_0006: ldc.i4.1这一行直接加载了数值1——在编译Program的时候,类库中Version常量的值。

有朋友可能想说,那重新编译一下Program不就解决问题了?可问题在于,.NET的一个重要原则就是部署容易和消除DLL陷阱。不能强制要求客户在类库升级后还要重新编译自己的程序。

所以,对于类库设计人员来说,当暴露一个公开的常量时要非常小心,只有确信一个值永远不会发生变化(包括今后的一系列升级)时,才能使用常量。否则请使用只读(只有get访问器)属性或只读(readonly)字段来返回这样的值。

posted on 2008-11-24 11:04 VocanoLee 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/VocanoLee/archive/2008/11/24/1339748.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值