1.2 - C#语言习惯 - 用运行时常量readonly而不是编译期常量const
C#中有两种类型的常量:编译期常量和运行时常量。二者有着截然不同的行为,使用不当将会带来性能上或正确性上的问题。
这两个问题最好都不要发生,不过若难以同时避免的话,那么一个略微慢一些但能保证正确的程序则要好过一个快速但不能正常工作的程序。
考虑到这些,你应该尽量使用运行时常量,而不是编译期常量。
虽然编译期常量略微快一些,但是却没有运行时常量那么灵活。应紧紧在那些性能异常敏感,且常量的值在各个版本之间绝对不会变化时,再使用编译期常量。
运行时常量使用readonly关键字声明,编译期常量则使用const关键字声明:
// Compile time constant public const string Name = "张董"; // Runtime constant ;
上述代码在类或struct的范围内演示了两种常量。编译期常量也可声明在方法中,而只读的运行时常量却不能声明在方法中。
编译期常量与运行时常量行为的不同之处在于对他们的访问方式不同。编译期常量的值是在目标代码中进行替换的。
下列代码:
if (user.Age == Age)
将与如下代码编译成同样的IL(中间语言):
)
运行时常量将在运行时求职。引用运行时常量生成的IL将引用到readonly的变量,而不是变量的值。
这个差别会带来几个限制,会影响到选用哪种类型的常量。
编译期常量仅能用于基本类型(内建的整数和浮点类型)、枚举或字符串。只有这些类型才允许我们在初始化器中指定有意义的常量值。
在代码编译后得到的IL代码中,只有这些常量可直接被替换为它们的字面值。
例如,下面的代码就不会通过编译。
即使要初始化的常量类型属于值类型,也无法在C#中使用new操作符来初始化编译期常量:
// Does not compile, use readonly instead , , , , , );
编译期常量const仅能用于数字和字符串。运行时常量readonly也是一种常量,因为在构造函数执行后未能被再次修改。
二者的区别在于,只读的值将在运行时给出,这自然会带来更好的灵活性。
例如,运行时常量可以为任意类型。运行时常量必须在构造函数或初始化器中初始化。
你可以让某个readonly值为一个DateTime结构,而不是指定某个const为DateTime。
你可以用readonly值保存实例常量,为类的每个实例存放不同的值。而编译期常量就是静态的常量。
二者最重要的区别在于,readonly值是在运行时解析的。引用一个readonly常量时声称的IL引用的是readonly变量,而不是其值。
这一点就会对日后的维护产生深远的影响。
编译期常量将生成同样的IL,就像直接在代码中给出数字一样,即使是跨程序集也是如此,即使另一个程序集中引用了某个程序集中的某个常量,相应常量也会被直接替换成这个值。
编译期常量与运行时常量的求值方式将会影响运行的兼容性。
假设我们在一个名为Infrastructure的程序集中分别定义了一个const字段和一个readonly字段:
public class UsefulValues
{
;
;
}
在另一个程序集中,我们使用了这两个值:
for(int i=UsefulValues.StartValue; i<UsefulValues.EndValue; i++)
{
Console.WriteLine("value is {0}", i);
}
执行该程序,输出结果如下:
value value ... value
过了一段时间,我们更新了Infrastructure程序集,做出了如下修改:
public class UsefulValues
{
;
;
}
随后,在分发Infrastructure程序集时,并没有重新编译整个应用程序的所有程序集,我们期待的是如下的输出:
value value ... value
但实际上,你却看不到任何输出。因为现在那个循环语句将使用105作为他的起始值,使用10作为他的结束条件。
C#编译器在第一次编译Application程序集时,将其中的EndValue替换成了它对应的常量值10.
而对于StartValue来说,因为它被声明为readonly,所以对其求值会发生在运行时。
因此,Application程序集在没有被重新编译的情况下,只要简单地更新一个Infrastructure程序集就够了。
相反,更改一个工友的编译期常量的值应该被看做是对类型接口的修改,你必须重新编译所有引用该常量的代码。
而更改只读常量的值缺紧紧算作是对类型实现的修改,它与其客户代码在二进制层次上是兼容的。
不过,有时你确实需要让某个值在编译时确定。
例如,考虑用一系列常量来标出对象的不同版本的序列化形式,其中这一组常量用来区分不同版本的对象。
这时,用来标记特定版本号的值就应该采用编译期常量,因为他们永远都不会改变。
而标记版本号的值就应该采用运行时常量,因为它的值会随着每个不同版本的发布而变化。
private const int Version1_0 = 0x0100; private const int Version1_1 = 0x0101; private const int Version1_2 = 0x0102; // major release private const int Version2_0 = 0x0200; // check for the current version private static readonly int CurrentVersion = Version2_0;
使用运行时常量在每个存盘文件中保存当前的版本:
// Read from persistent storage, check stored version against compile-time constant:
protected MyType(Serialization info, StreamingContext cntxt)
{
int storedVersion = info.GetInt32("VERSION");
switch(storedVersion)
{
case Version2_0:
readVersion2(info, cntxt);
break;
case Version1_1:
readVersion1Dot1(info, cntxt);
break;
// etc.
}
}
// Write the current version
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo inf, StreamingContext cxt)
{
// use runtime constant for current version:
inf.AddValue("VERSION", CurrentVersion);
// write remaining elements...
}
相比之下,const最终优于readonly的地方就是性能。使用已知常量值要比访问readonly值略微高效一些。
不过这其中的效率提升可以说是微乎其微,而这样却能降低很多的灵活性。
若是你最终还是计划要放弃灵活性,那么在决定之前一定要对两者的性能差别做一个实际的测试。
在使用具名和可选参数时,你也会遇到同样的运行时/编译期常量的取舍。
可选参数的默认值将被放置于调用端,就像编译期常量(用const声明)的默认值一样。
正如选用readonly和const一样,在修改可选参数的值时也必须小心操作。
在编译期必须得到确定数值时一定要使用const。
例如特性(attribute)的参数和枚举的定义等,还有那些在各个版本发布之间不会变化的值。
在除此之外的所有情况下,都应尽量选择更加灵活的readonly常量。
1.2 - C#语言习惯 - 用运行时常量readonly而不是编译期常量const的更多相关文章
- 《C#高效编程》读书笔记02-用运行时常量(readonly)而不是编译期常量(const)
C#有两种类型的常量:编译期常量和运行时常量.两者有截然不同的行为,使用不当的话,会造成性能问题,如果没法确定,则使用慢点,但能保证正确的运行时常量. 运行时常量使用readonly关键字声明,编译期 ...
- 改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员(整理中)
改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员 序 为什么我们的程序运行得棒棒的,还要改呢?Why? 答:我们要让程序运行得更快,执行的效率更高,代码的可读性更强,维护的成本更低.. ...
- 通过编写c语言程序,运行时实现打印另一个程序的源代码和行号
2017年6月1日程序编写说明: 1.实现行号的打印,实现代码的读取和输出,理解主函数中的参数含义. 2.对fgets函数理解不够 3.对return(1); return 0的含义理解不够 4.未实 ...
- 正确理解java编译时,运行时以及构建时这三个概念
Java中的许多对象(一般都是具有父子类关系的父类对象)在运行时都会出现两种类型:编译时类型和运行时类型,例如:Person person = new Student();这行代码将会生成一个pers ...
- 深入理解java:1.3.1 JVM内存区域的划分(运行时数据区)
学习Java GC机制,可以帮助我们在日常工作中 排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高效的程序. 我们将从4个方面学习Java GC机制, 1,内存是如何分配的: 2, ...
- JVM学习-运行时数据区域
目录 前言 运行时数据区 程序计数器 Java虚拟机栈 局部变量表 基础数据类型 对象引用 returnAddress 操作数栈 动态链接 方法返回地址 Java堆 方法区 类型信息 字段描述符 方法 ...
- [.NET] 《C# 高效编程》(一) - C# 语言习惯
C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Con ...
- [.NET] 《Effective C#》快速笔记(一)- C# 语言习惯
<Effective C#>快速笔记(一)- C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 ...
- 《Effective C#》快速笔记(一)- C# 语言习惯
目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional ...
随机推荐
- PropertyGrid控件由浅入深(一):文章大纲
Winform中PropertyGrid控件是一个非常好用的对象属性编辑工具,对于Key-Value形式的数据的处理也是非常的好用. 因为Property控件设计良好,在很小的空间内可以展示很多的内容 ...
- ASP.NET MVC Model验证(二)
ASP.NET MVC Model验证(二) 前言 上篇内容演示了一个简单的Model验证示例,然后在文中提及到Model验证在MVC框架中默认所处的位置在哪?本篇就是来解决这个问题的,并且会描述一下 ...
- CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探
CSharpGL(8)使用3D纹理渲染体数据 (Volume Rendering) 初探 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码 ...
- Java笔记——Java8特性之Lambda、方法引用和Streams
Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...
- SSH框架整合(代码加文字解释)
一.创建数据库并设置编码. A) create database oa default character set utf8. 二.MyEclipse工程 A) 在Myeclipse里创建web工程, ...
- Notes:SVG(3)---滤镜和渐变
SVG滤镜使用filter标签来定义,该标签必须嵌套在defs元素里面,并且必须指定一个ID,以供引用. 在 SVG 中,可用的滤镜有: feBlend feColorMatrix feCompone ...
- JavaScript中尺寸、坐标
测试环境是IE8,Chrome38,Firefox40,下面是全局通用脚本打印代码 /** * 打印 */ function write(str) { document.write(str + '&l ...
- 图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用
图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 ...
- 探寻<a>中的href和onclick
一.知识点: onclick的方法参数必须加引号 href跳转参数有长度限制 href中执行js会把encodeURIComponent()编码之后的东西自动解码,有时会影响参数传递 a标签中的onc ...
- 在thinkphp中,写的博文标签多对多关系的标签频率统计算法
常常看到别人的博客里面,或者网站里面有这样随机颜色,但字体大小与标签出现频率有关的标签云,于是自己就想写一个.至于颜色的随机显示,那就很简单了,这里就不列代码. 因为正在学thinkphp,所以数据查 ...