编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)

众所周知,.net 包含 2 种类型的常量,运行时常量和编译时常量,它们的表现行为不同,使用不当,会使你陷入困境。虽然编译时常量在执行上速度略快,但我依然强烈建议大家使用运行时常量(readonly),而不是编译时常量(const)。在继续深入了解之前,我们先要知道 .net 中两种常量各自的特点。

编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const),本文由比特飞原创发布,转载务必在文章开头附带链接:https://www.byteflying.com/archives/6549

该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。

1、概述

众所周知,.net 包含两种类型的常量,运行时常量编译时常量,它们的表现行为不同,使用不当,会使你陷入困境。虽然编译时常量在执行上速度略快,但我依然强烈建议大家使用运行时常量(readonly),而不是编译时常量(const)。在继续深入了解之前,我们先要知道 .net 中两种常量各自的特点。

2、.net中两种常量的基本特点

我们看看以下代码片段:

编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
两种常量的代码片段

我们很容易总结出它们各自的特点:

1、运行时常量 readonly 要么在定义的时候赋初始值,要么在构造函数中赋初始值;

2、编译时常量 const 必须在定义的时候赋初始值。

那我为什么建议大家使用运行时常量呢?因为编译时常量可能会使你已发行程序的表现和你测试时不同,这是为什么呢?为了清楚的了解个中原委,我们要明白编译器为运行时常量和编译时常量都做了什么?

3、编译器为readonly和const关键字做了什么?

使用readonly修饰的常量为运行时常量,使用const修饰的常量为编译时常量,我们先来看两段示例代码:

public static readonly int ViewCount1 = 100;
public const int ViewCount2 = 100;

我们再来看一下两段代码的IL:

编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
使用 DnSpy 解密的 IL 1
编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
使用 DnSpy 解密的 IL 2
编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
DnSpy 的反编译结果

从上面的反编译结果,可以明显的看出 const 为编译时常量,在编译期间已经被写到IL中。而 readonly 为运行时常量。Microsoft 技术支持文档中相关 IL 的解释也可以印证这一点。

编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
Microsoft 技术支持文档中 ldsfld 的解释
编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
Microsoft 技术支持文档中 Ldc_I4_S 的解释

当然,对于这个案例,输出的结果是一致的:

编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
ViewCount1 和 ViewCount2 的输出结果

于是,我们得到以下重要结论:

1、constreadonly 在初始化一次后,不能再赋值;

2、const 在编译时被编译器所替换,而 readonly 在编译时只做校验,不作替换处理,运行时表现如同一般变量。

3、const 在 .net 中只能用于 数值型字符型null,没有例外。

我们再看一个简单的示例:

 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、总结

1、运行时常量 readonly 要么在定义的时候赋初始值,要么在构造函数中赋初始值;

2、编译时常量 const 必须在定义的时候赋初始值;

3、const 和 readonly 在初始化一次后,不能再赋值;

4、const 在编译时被编译器所替换,而 readonly 在编译时只做校验,不作替换处理,运行时表现如同一般变量;

5、const 在 .net 中只能用于 数值型、字符型和 null,没有例外;

6、尽量使用 readonly 常量,避免遇到已发行软件无法按预期运行的问题。

开发人员应牢记以上开发守则,否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你、唾弃你

该系列文章由比特飞原创发布,计划用半年时间写完全50篇文章,为大家提供编写高质量代码的一般准则。

本文由 .Net中文网 原创发布,欢迎大家踊跃转载。

转载请注明本文地址:https://www.byteflying.com/archives/6549

发表评论

登录后才能评论