readonly vs. const [C#]
readonly vs. const [C#]
Updated on Friday, October 29, 2004
Written by Allen Lee
Features:
readonly
和const
都是用来标识常量的[1]
。
const
可用于修饰class的field或者一个局部变量(local variable);而readonly仅仅用于修饰class的field。
const
常量的值必定在编译时就已明确并且恒定的;而readonly
常量却有一点不同,那就是其值可以在运行时编译,当然,它也必须遵守作为常量的约束,那就是值必须恒定不变。
const
常量必须在声明的同时对其进行赋值,并且确保该值在编译时可确定并恒定;而readonly常量则可以根据情况选择在声明的同时对其赋予一个编译时确定并恒定的值,或者将其值的初始化工作交给实例构造函数(instant constructor)完成。如:public readonly string
m_Now = DateTime.Now.ToString();,m_Now会随着运行时实际情况变化而变化。
const
常量属于类级别(class level)而不是实例对象级别(instant object level),并且它不能跟static
结合一起使用,该常量的值将由整个类的所有实例对象共同分享(详细论述参见后面的Remark区域)。
readonly
常量既可以是类级别也可以是实例对象级别的,这取决于它的声明以及初始化工作怎么实施。readonly
可以与static
结合使用,用于指定该常量属于类级别,并且把初始化工作交由静态构造函数(static constructor)完成(有关如何把readonly
常量声明为类级别或实例对象级别的论述清参见后面的Remark区域)
。 能被const
修饰声明为常量的类型必须是以下的基元类型(primitive type):sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, float
, bool
, decimal
, string
。
object
, 数组(Array)和结构(struct)不能被声明为const
常量。
一般情况下,引用类型是不能被声明为const
常量的,不过有一个例外:string
。该引用类型const
常量的值可以有两种情况,string
或null
。其实,string
虽然是引用类型,但是.NET却对它特别处理,这种处理叫做字符串恒定性(immutable),使得string
的值具有只读特性。有关字符串恒定性的内容,可以参考《Microsoft .NET框架程序设计(修订版)》
。
?
Examples:
using?System;![](/Images/OutliningIndicators/None.gif)
public?class?Order![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
????public?Order()
????
{
????????Guid?guid?=?Guid.NewGuid();
????????ID?=?guid.ToString("D");
????}![](/Images/OutliningIndicators/InBlock.gif)
????//?对于每一份订单,其订单序号都是实时确定的常量。
????public?readonly?string?ID;![](/Images/OutliningIndicators/InBlock.gif)
????public?override?string?ToString()
????
{
????????return?"Order?ID:?"?+?ID;
????}
}
Explaintion:
如果结合数据库使用,ID field通常都会都会与某个表的主健(primary key)关联起来,如Orders表的OrderID。
数据库的主健通常采用以下三种方式:
自动递增值。你可以通过把DataColumn.AutoIncrement设定为true值来激活自动递增特性。
唯一名称。这个是使用自己定义的算法来生成一个唯一序列号。
GUID(全局唯一标识符)。你可以通过System.Guid结构来生成GUID,如上例。
using
?System;![](/Images/OutliningIndicators/None.gif)
class
?Customer![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
????public?Customer(string?name,?int
?kind)
????![](/Images/dot.gif)
{
????????m_Name?=
?name;
????????m_Kind?=
?kind;
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
public?const?int?NORMAL?=?0
;
????public?const?int?VIP?=?1
;
????public?const?int?SUPER_VIP?=?2
;![](/Images/OutliningIndicators/InBlock.gif)
????private?string
?m_Name;
????public?string
?Name
????![](/Images/dot.gif)
{
????????get?
{?return?m_Name;?}
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
private?readonly?int
?m_Kind;
????public
?int
?Kind
????![](/Images/dot.gif)
{
????????get?
{?return?m_Kind;?}
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
public?override?string
?ToString()
????![](/Images/dot.gif)
{
????????if(m_Kind?==
?SUPER_VIP)
????????????return?"Name:?"?+?m_Name?+?"[SuperVip]"
;
????????else?if(m_Kind?==
?VIP)
????????????return?"Name:?"?+?m_Name?+?"[Vip]"
;
????????else
????????????
return?"Name:?"?+?m_Name?+?"[Normal]"
;
????}
}
?
Remarks:
一般情况下,如果你需要声明的常量是普遍公认的并作为单个使用,例如圆周率,黄金分割比例等。你可以考虑使用const
常量,如:public const double
PI = 3.1415926;。如果你需要声明常量,不过这个常量会随着实际的运行情况而决定,那么,readonly
常量将会是一个不错的选择,例如上面第一个例子的订单号Order.ID。
另外,如果要表示对象内部的默认值的话,而这类值通常是常量性质的,那么也可以考虑const
。更多时候我们对源代码进行重构时(使用Replace Magic Number with Symbolic Constant),要去除魔数(Magic Number)的影响都会借助于const
的这种特性。
对于readonly
和const
所修饰的变量究竟是属于类级别的还是实例对象级别的问题,我们先看看如下代码:
using?System;![](/Images/OutliningIndicators/None.gif)
namespace?ConstantLab![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
????class?Program
????
{
????????static?void?Main(string[]?args)
????????
{
????????????Constant?c?=?new?Constant(3);
????????????Console.WriteLine("ConstInt?=?"?+?Constant.ConstInt.ToString());
????????????Console.WriteLine("ReadonlyInt?=?"?+?c.ReadonlyInt.ToString());
????????????Console.WriteLine("InstantReadonlyInt?=?"?+?c.InstantReadonlyInt.ToString());
????????????Console.WriteLine("StaticReadonlyInt?=?"?+?Constant.StaticReadonlyInt.ToString());![](/Images/OutliningIndicators/InBlock.gif)
????????????Console.WriteLine("Press?any?key?to?continue");
????????????Console.ReadLine();
????????}
????}![](/Images/OutliningIndicators/InBlock.gif)
????class?Constant
????
{
????????public?Constant(int?instantReadonlyInt)
????????
{
????????????InstantReadonlyInt?=?instantReadonlyInt;
????????}![](/Images/OutliningIndicators/InBlock.gif)
????????public?const?int?ConstInt?=?0;![](/Images/OutliningIndicators/InBlock.gif)
????????public?readonly?int?ReadonlyInt?=?1;![](/Images/OutliningIndicators/InBlock.gif)
????????public?readonly?int?InstantReadonlyInt;![](/Images/OutliningIndicators/InBlock.gif)
????????public?static?readonly?int?StaticReadonlyInt?=?4;
????}
}
使用Visual C#在Main()里面使用IntelliSence插入Constant的相关field的时候,发现ReadonlyInt和InstantReadonlyInt需要指定Constant的实例对象;而ConstInt和StaticReadonlyInt却要指定Constant class(参见上面代码)。可见,用const
或者static readonly
修饰的常量是属于类级别的;而readonly
修饰的,无论是直接通过赋值来初始化或者在实例构造函数里初始化,都属于实例对象级别。
一般情况下,如果你需要表达一组相关的编译时确定常量,你可以考虑使用枚举类型(enum),而不是把多个const
常量直接嵌入到class中作为field,不过这两种方式没有绝对的孰优孰劣之分。
?
using
?System;![](/Images/OutliningIndicators/None.gif)
enum
?CustomerKind![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
????SuperVip,
????Vip,
????Normal
}
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
class
?Customer![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
????public?Customer(string
?name,?CustomerKind?kind)
????![](/Images/dot.gif)
{
????????m_Name?=
?name;
????????m_Kind?=
?kind;
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
private?string
?m_Name;
????public?string
?Name
????![](/Images/dot.gif)
{
????????get?
{?return?m_Name;?}
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
private
?CustomerKind?m_Kind;
????public
?CustomerKind?Kind
????![](/Images/dot.gif)
{
????????get?
{?return?m_Kind;?}
????}
![](/Images/OutliningIndicators/InBlock.gif)
????
public?override?string
?ToString()
????![](/Images/dot.gif)
{
????????return?"Name:?"?+?m_Name?+?"["?+?m_Kind.ToString()?+?"]"
;
????}
}
?
然而,当这种结合使用枚举和条件判断的代码阻碍了你进行更灵活的扩展,并有可能导致日后的维护成本增加,你可以代之以多态,使用Replace Conditional with Polymorphism来对代码进行重构。(有关多态的详细介绍,请参见《今天你多态了吗?》一文。)
?
Comments:
readonly field准确来说应该翻译成为“只读域”,这里是为了统一翻译用语才将它和const两者所修饰的量都说成“常量”,希望没有引起误会。
?
Updated on Friday, October 29, 2004
Written by Allen Lee
Features:
readonly
和const
都是用来标识常量的[1]
。
const
可用于修饰class的field或者一个局部变量(local variable);而readonly仅仅用于修饰class的field。
const
常量的值必定在编译时就已明确并且恒定的;而readonly
常量却有一点不同,那就是其值可以在运行时编译,当然,它也必须遵守作为常量的约束,那就是值必须恒定不变。
const
常量必须在声明的同时对其进行赋值,并且确保该值在编译时可确定并恒定;而readonly常量则可以根据情况选择在声明的同时对其赋予一个编译时确定并恒定的值,或者将其值的初始化工作交给实例构造函数(instant constructor)完成。如:public readonly string
m_Now = DateTime.Now.ToString();,m_Now会随着运行时实际情况变化而变化。
const
常量属于类级别(class level)而不是实例对象级别(instant object level),并且它不能跟static
结合一起使用,该常量的值将由整个类的所有实例对象共同分享(详细论述参见后面的Remark区域)。
readonly
常量既可以是类级别也可以是实例对象级别的,这取决于它的声明以及初始化工作怎么实施。readonly
可以与static
结合使用,用于指定该常量属于类级别,并且把初始化工作交由静态构造函数(static constructor)完成(有关如何把readonly
常量声明为类级别或实例对象级别的论述清参见后面的Remark区域)
。 能被const
修饰声明为常量的类型必须是以下的基元类型(primitive type):sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, float
, bool
, decimal
, string
。
object
, 数组(Array)和结构(struct)不能被声明为const
常量。
一般情况下,引用类型是不能被声明为const
常量的,不过有一个例外:string
。该引用类型const
常量的值可以有两种情况,string
或null
。其实,string
虽然是引用类型,但是.NET却对它特别处理,这种处理叫做字符串恒定性(immutable),使得string
的值具有只读特性。有关字符串恒定性的内容,可以参考《Microsoft .NET框架程序设计(修订版)》
。
?
Examples:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
Explaintion:
如果结合数据库使用,ID field通常都会都会与某个表的主健(primary key)关联起来,如Orders表的OrderID。
数据库的主健通常采用以下三种方式:
自动递增值。你可以通过把DataColumn.AutoIncrement设定为true值来激活自动递增特性。
唯一名称。这个是使用自己定义的算法来生成一个唯一序列号。
GUID(全局唯一标识符)。你可以通过System.Guid结构来生成GUID,如上例。
![](/Images/OutliningIndicators/None.gif)
?System;
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
?Customer
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
?kind)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
?name;
![](/Images/OutliningIndicators/InBlock.gif)
?kind;
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
public?const?int?NORMAL?=?0
;
![](/Images/OutliningIndicators/InBlock.gif)
;
![](/Images/OutliningIndicators/InBlock.gif)
;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
?m_Name;
![](/Images/OutliningIndicators/InBlock.gif)
?Name
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
private?readonly?int
?m_Kind;
![](/Images/OutliningIndicators/InBlock.gif)
?int
?Kind
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
public?override?string
?ToString()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
?SUPER_VIP)
![](/Images/OutliningIndicators/InBlock.gif)
;
![](/Images/OutliningIndicators/InBlock.gif)
?VIP)
![](/Images/OutliningIndicators/InBlock.gif)
;
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
return?"Name:?"?+?m_Name?+?"[Normal]"
;
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
?
Remarks:
一般情况下,如果你需要声明的常量是普遍公认的并作为单个使用,例如圆周率,黄金分割比例等。你可以考虑使用const
常量,如:public const double
PI = 3.1415926;。如果你需要声明常量,不过这个常量会随着实际的运行情况而决定,那么,readonly
常量将会是一个不错的选择,例如上面第一个例子的订单号Order.ID。
另外,如果要表示对象内部的默认值的话,而这类值通常是常量性质的,那么也可以考虑const
。更多时候我们对源代码进行重构时(使用Replace Magic Number with Symbolic Constant),要去除魔数(Magic Number)的影响都会借助于const
的这种特性。
对于readonly
和const
所修饰的变量究竟是属于类级别的还是实例对象级别的问题,我们先看看如下代码:
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
使用Visual C#在Main()里面使用IntelliSence插入Constant的相关field的时候,发现ReadonlyInt和InstantReadonlyInt需要指定Constant的实例对象;而ConstInt和StaticReadonlyInt却要指定Constant class(参见上面代码)。可见,用const
或者static readonly
修饰的常量是属于类级别的;而readonly
修饰的,无论是直接通过赋值来初始化或者在实例构造函数里初始化,都属于实例对象级别。
一般情况下,如果你需要表达一组相关的编译时确定常量,你可以考虑使用枚举类型(enum),而不是把多个const
常量直接嵌入到class中作为field,不过这两种方式没有绝对的孰优孰劣之分。
?
![](/Images/OutliningIndicators/None.gif)
?System;
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
?CustomerKind
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
class
?Customer
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
?name,?CustomerKind?kind)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
?name;
![](/Images/OutliningIndicators/InBlock.gif)
?kind;
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
private?string
?m_Name;
![](/Images/OutliningIndicators/InBlock.gif)
?Name
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
private
?CustomerKind?m_Kind;
![](/Images/OutliningIndicators/InBlock.gif)
?CustomerKind?Kind
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/InBlock.gif)
![](/Images/OutliningIndicators/InBlock.gif)
public?override?string
?ToString()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
![](/Images/dot.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
;
![](/Images/OutliningIndicators/ExpandedSubBlockEnd.gif)
![](/Images/OutliningIndicators/ExpandedBlockEnd.gif)
?
然而,当这种结合使用枚举和条件判断的代码阻碍了你进行更灵活的扩展,并有可能导致日后的维护成本增加,你可以代之以多态,使用Replace Conditional with Polymorphism来对代码进行重构。(有关多态的详细介绍,请参见《今天你多态了吗?》一文。)
?
Comments:
readonly field准确来说应该翻译成为“只读域”,这里是为了统一翻译用语才将它和const两者所修饰的量都说成“常量”,希望没有引起误会。
?