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

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

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

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

代码1 - 老刘的“类库”

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

csc /t:library Library.cs

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

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

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

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中的常量定义

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

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

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

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

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

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

返回目录:透过IL看C#

Tag标签: CLR,CLI,C#
posted @ 2008-11-23 10:05 Anders Liu 阅读(2653) 评论(37)  编辑 收藏 网摘 所属分类: CLR / CLI
Body:78.125,BeforeCate:78.125,93.75

  回复  引用  查看    
#1楼2008-11-23 10:13 | CoderZh      
原来还有这样的情况,学习了!
以后用常量要小心了。。。

  回复  引用  查看    
#2楼2008-11-23 10:21 | Nicholas Yuen      
我就是那个倒霉的家伙

:)

  回复  引用  查看    
#3楼2008-11-23 10:25 | SuperWulei      

  回复  引用  查看    
#4楼[楼主]2008-11-23 10:33 | Anders Liu      
@CoderZh
@Nicholas Yuen

是啊,我自己也当了一次倒霉的家伙。

  回复  引用  查看    
#5楼2008-11-23 11:17 | Anders Cui      
@Anders Liu
我还没这么倒霉,呵呵
不过这里的常量还是“可以由我变,但不能由你变”啊
客户代码仍然不能改变它

  回复  引用  查看    
#6楼2008-11-23 11:41 | canbeing      
真是防不胜防啊,呵呵
真的要小心

  回复  引用  查看    
#7楼[楼主]2008-11-23 12:03 | Anders Liu      
@Anders Cui

这里的常量是“你我都不能变”啦。



  回复  引用  查看    
#8楼[楼主]2008-11-23 12:03 | Anders Liu      
@canbeing
是的。

  回复  引用  查看    
#9楼2008-11-23 12:12 | 横刀天笑      
说的生动啊,呵呵
  回复  引用  查看    
#10楼2008-11-23 12:42 | 丁学      
还是 Erlang 好啊,最近喜欢上了,嘿嘿
  回复  引用  查看    
#11楼2008-11-23 12:43 | try      
public const int Version = 1;
改成
private const int Version = 1;

  回复  引用  查看    
#12楼2008-11-23 12:45 | Anders Cui      
@丁学
Erlang没这样的问题吗?常量值改了,不还得重新编译?

  回复  引用  查看    
#13楼2008-11-23 12:54 | 上不了岸的鱼{ttzhang}      
学习了,不小心的话,说不定真的会中奖,呵呵...
《.net框架 程序设计》中有讲!
  回复  引用  查看    
#15楼2008-11-23 13:14 | Q.Lee.lulu      
你必须知道的.NET好像有说过这个问题吧..
  回复  引用  查看    
#16楼2008-11-23 13:35 | 丁学      
@Anders Cui
Erlang的变量是不可变得,哈哈

  回复  引用  查看    
#17楼[楼主]2008-11-23 14:02 | Anders Liu      
@一个喜欢编程的老人家
@Q.Lee.lulu

嗯,重申一下而已,这个场景比较简单明了。而且自己遇到的体会深呐。。。

  回复  引用  查看    
#18楼2008-11-23 14:19 | airwolf2026      
呃...留名...嘎嘎.
  回复  引用  查看    
#19楼2008-11-23 14:30 | Anders Cui      
@丁学
你说的这个我知道,呵呵,函数式编程嘛
但是Liu说的情况是,类库的源代码改了:)

  回复  引用  查看    
#20楼2008-11-23 14:33 | XMLSpy      
我们项目中已经禁止使用Const关键字了.
使用 static readonly

  回复  引用  查看    
#21楼2008-11-23 18:34 | xwang      
呵呵 讲的不错 这个是比较容易出错 :)
  回复  引用  查看    
#22楼2008-11-23 19:08 | Jeffrey Zhao      
简单的说,常量是直接编译进值的,Readonly是每次去取得……
  回复  引用    
#23楼2008-11-23 19:11 | xiaop(匿名)[未注册用户]
static readonly 就可以解决这个问题了! const 是编译得到的,所以有这样的问题。
  回复  引用    
#24楼2008-11-23 19:11 | xiaop(匿名)[未注册用户]
呀,没有看见老找同学的回复。。。
  回复  引用  查看    
#25楼2008-11-23 19:12 | Justina Chen      
也遇到过这样的问题,编译时刻初始化,就已经能看懂了
  回复  引用  查看    
#26楼2008-11-23 20:25 | xjb      
研究的很深呀
  回复  引用  查看    
#27楼2008-11-23 20:59 | o﹎箜絔┌↘      
学习啦。。。
  回复  引用    
#28楼2008-11-23 23:19 | 嘿嘿嘿[未注册用户]
这个问题在反编译一些源码的常常遇到,常量本质就是个编译期的宏定义。
  回复  引用  查看    
#29楼2008-11-24 09:15 | mrfangzheng      
const就是编译期替换 和define类似
老刘的代码必须重新编译才行

  回复  引用    
#30楼2008-11-24 09:36 | wimdys[未注册用户]
如果重新编译项目是不是可以解决这个产量问题?
  回复  引用  查看    
#31楼[楼主]2008-11-24 10:05 | Anders Liu      
@mrfangzheng
@wimdys

是的,重新编译可以解决问题。但是,.NET的设计目标是一次编写到处运行,你升级了类库,不应要求所有的客户重新编译他们的代码。

  回复  引用  查看    
#32楼2008-11-24 11:10 | LanceZhang      
写得太好了,O(∩_∩)O哈哈~
  回复  引用  查看    
#33楼2008-11-24 14:50 | sky-v      
呵呵,不错,学习了。。。
  回复  引用  查看    
#34楼2008-11-30 23:48 | 啊不才      
的确,这一点很有迷惑性,值得注意
  回复  引用  查看    
#35楼2008-12-01 14:08 | virus      
可恶的微软
简化了我们的工作
在下面偷偷做了很多的工作
表面看起来方便了我们
可是隐瞒了很多

  回复  引用  查看    
#36楼2008-12-04 19:15 | DOGNET      
刘老师好:问你个问题?

假定有三个自定义类A、B、C
若类A有一个属性A.P的类型是集合List<B>
而C中有一个静态方法C.M (Object o) 以object基类型实体作为输入参数,而A是o的一种可能

在M方法中我们可通过O.GetType().GetProperties() 得到O的PropertyInfo[] 集合

遍历此集合找到其中的一个属性P1是List<T>类型(调用此判断条件(pi.PropertyType.FullName.StartsWith("System.Collections.Generic.List") !=-1))

请问我如何得到这个T的类型(全名),并能构造此集合List<T>以便将其传给C的另一个静态方法(以List<T>为参数)进行下一步的处理

  回复  引用  查看    
#37楼2009-01-04 16:49 | yatasoft      
Effiective C#条款2运行时常量(readonly)优于编译时常量(const)。
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1339257 mEMiYmNG7ow=



相关文章:


相关搜索:
CLR CLI C# CLR / CLI

相关链接: