编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const),本文由比特飞原创发布,转载务必在文章开头附带链接:https://www.byteflying.com/archives/6549
该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。
1、概述
众所周知,.net 包含 2 种类型的常量,运行时常量和编译时常量,它们的表现行为不同,使用不当,会使你陷入困境。虽然编译时常量在执行上速度略快,但我依然强烈建议大家使用运行时常量(readonly),而不是编译时常量(const)。在继续深入了解之前,我们先要知道 .net 中两种常量各自的特点。
2、.net中两种常量的基本特点
我们看看以下代码片段:
两种常量的代码片段
我们很容易总结出它们各自的特点:
那我为什么建议大家使用运行时常量呢?因为编译时常量可能会使你已发行程序的表现的和你测试时不同,这是为什么呢?为了清楚的了解个中原委,我们要明白编译器为运行时常量和编译时常量都做了什么?
3、编译器为readonly和const关键字做了什么?
使用readonly修饰的常量为运行时常量,使用const修饰的常量为编译时常量,我们先来看两段示例代码:
public static readonly int ViewCount1 = 100;
public const int ViewCount2 = 100;
我们再来看一下两段代码的IL:
使用 DnSpy 解密的 IL 1
使用 DnSpy 解密的 IL 2
DnSpy 的反编译结果
从上面的反编译结果,可以明显的看出 const 为编译时常量,在编译期间已经被写到IL中。而 readonly 为运行时常量。Microsoft 技术支持文档中相关 IL 的解释也可以印证这一点。
Microsoft 技术支持文档中 ldsfld 的解释
Microsoft 技术支持文档中 Ldc_I4_S 的解释
当然,对于这个案例,输出的结果是一致的:
ViewCount1 和 ViewCount2 的输出结果
于是,我们得到以下重要结论:
我们再看一个简单的示例:
public const int Birthday= 1986;
if(DateTime.UtcNow.Year == 1986) if(DateTime.UtcNow.Year == Birthday)
由我们之前的分析可以得知,它们是完全相同的。然而,并不是所有的情况下,你都能得到你所期望的结果,这又是为什么呢?
4、运行时所给出的结果,并不是你所期望的结果
某些情况下,运行时给出的结果与你所期望的结果大相径庭。现在我们有以下场景,你有一个已交付的软件,软件包含一个可执行主程序 EffectiveCoding02.exe ,一个被主程序所引用的类库 EffectiveCoding02.dll,主程序中包含以下代码:
public class SomeClass{ public static readonly int StartValue = 100; public const int EndValue = 105; }
类库中包含以下代码:
for(var i = SomeClass.StartValue; i < SomeClass.EndValue; i++){ console.WriteLine($"value = {i}"); }
当初次交付程序给客户的时候,以上代码完全按照你的预期运行。后来由于软件迭代,你修改了主程序:
public class SomeClass{ public static readonly int StartValue = 110; public const int EndValue = 115; }
重新编译后,因为你只更改了这个主程序,所以你将这个主程序交付给客户,然而你却得不到任何输出内容,程序没有按照你所预期的那样工作,于是你会说出那句开发人员的至理名言,“我这里是正常的啊!”。
产生这个问题的原因在于,由于编译时常量 const 是在编译时被编译器所替换的,所以虽然你进行了编译操作,然而用户计算机中的类库由于没有被编译器重新编译,所以用户计算机中的类库中的 EndValue 还是上一次编译时的结果,即105。然而运行时常量却以正常的方式运作,所以它的值变成了110,自然,你得不到任何的输出结果。因为循环初始值大于结束值。
5、总结
开发人员应牢记以上开发守则,否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你、唾弃你。
该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。