C#中const和readonly的区别

原创
小哥 2年前 (2023-05-22) 阅读数 2 #大杂烩

转自: http://www.cnblogs.com/zcy_soft/archive/2010/10/14/1851002.html

C#引入了readonly表示只读域的修饰符,const表示不变常量。顾名思义,只读字段不能写入,不变常量不能修改。两者有什么区别?只读域只能初始化--声明初始化或构造函数初始化--在赋值过程中,只读字段的赋值操作不能在其他地方执行,否则编译器将报告错误。只读域可以是实例域,也可以是静态域。只读域的类型可以是C#任何类型的语言。但const修改后的常量必须与声明同时赋值,并要求编译器能够执行编译时间 计算 赋予此一定的价值。const修改后的常量是一个静态变量,对象无法获取。const修改后的值的类型也有限制,只能是以下类型之一(也可以转换为以下类型):sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, enum类型, 或引用类型。值得注意的是,这里的引用类型,由于删除了string除类型外,所有类型都出去null编译器在编译过程中无法准确计算值,因此我们可以将它们声明为const的引用类型只能是string或值为null其他引用类型。显然,当我们声明null使用常量时,我们失去了声明的含义 --这也可以说是C#设计的尴尬!

这意味着当我们需要一个const当它是一个常量,但它的类型限制了它在编译过程中计算某个值的能力时,我们可以将其声明为static readonly为了解决它。但两者之间仍然略有区别。查看以下两个不同的文件:

//file1.cs
//csc /t:library file1.cs
using System;
namespace MyNamespace1
{
public class MyClass1
{
public static readonly int myField = 10;
}
}

//file2.cs
//csc /r:file1.dll file2.cs
using System;
namespace MyNamespace2
{
public class MyClass1
{
public static void Main()
{
Console.WriteLine(MyNamespace1.MyClass1.myField);
}
}
}

我们的两个类属于两个文件file1.cs 和file2.cs并单独编译。在文件中file1.cs内的域myField声明为static readonly如果我们因某些需求而改变myField的值为20我们只需要重新编译文件file1.cs为file1.dll,在执行 file2.exe我们会得到20。但如果我们要static readonly改变为const之后,再次更改它myField初始化 的值时,我们必须重新编译所有对file1.dll否则,我们将参考 MyNamespace1.MyClass1.myField它不会像我们希望的那样改变。这在大型系统的开发过程中尤为重要。其实,如果我们能理解 const修改后的常量在编译时计算以确定其值,并在引用它的所有位置替换readonly仅在运行时确定的数量--我们只是不希望它的值在初始化后发生变化,所以我们可以理解C#只有通过设计师的辛勤和奉献,才能充分把握const和readonly的行为!


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;

public class Order
{
public Order()
{
Guid guid = Guid.NewGuid();
ID = guid.ToString("D");
}

// 对于每一份订单,其订单序号都是实时确定的常量。
public readonly string ID;

public override string ToString()
{
return "Order ID: " + ID;
}
}
Explaintion:

如果与数据库结合使用,ID field通常,它与某个表的主键相关联(primary key)相关,如Orders表的OrderID。
数据库的主键通常采用以下三种方法:
自动增值交付。您可以通过以下方式执行此操作DataColumn.AutoIncrement设定为true值以激活自动增量功能。
唯一名称。这是使用自定义算法来生成唯一的序列号。
GUID(全局唯一标识符)。您可以使用System.Guid要生成的结构GUID如上一个示例所示。
using System;

class Customer
{
public Customer(string name, int kind)
{
m_Name = name;
m_Kind = kind;
}

public const int NORMAL = 0;
public const int VIP = 1;
public const int SUPER_VIP = 2;

private string m_Name;
public string Name
{
get { return m_Name; }
}

private readonly int m_Kind;
public int Kind
{
get { return m_Kind; }
}

public override string ToString()
{
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:

一般来说,如果你需要声明的常量一般被识别并作为单个变量使用,比如 pi、黄金比例等。您可以考虑使用const常量,例如:public const double PI = 3.1415926;。如果你需要声明一个常量,但它将取决于实际操作,那么,readonly常量将是一个不错的选择,例如上面第一个示例中的订单号Order.ID。
此外,如果要表示对象中的默认值(通常是常量属性),还可以考虑const。更常见的是,当我们重构源代码(使用Replace Magic Number with Symbolic Constant)删除幻数(Magic Number)影响将得到以下帮助const这个特点。
对于readonly和const为了确定被修改的变量是属于类级别还是实例对象级别,让我们首先看一下以下代码:

使用Visual C#在Main()里面使用IntelliSence插入Constant的相关field当我发现ReadonlyInt和 InstantReadonlyInt需要指定Constant的实例对象;和ConstInt和StaticReadonlyInt却要指定 Constant class(请参阅上面的代码)。可见,使用const或者static readonly修改后的常量属于类级别;和readonly装饰,无论是直接通过赋值初始化还是在实例构造函数中初始化,它都属于实例对象级别。
一般来说,如果需要表达一组相关的编译时确定性常量,可以考虑使用枚举类型(enum)而不是放置多个const常量直接嵌入到class中作为 field但是,这两种方法之间没有绝对的优劣之分。

using System;

enum CustomerKind
{
SuperVip,
Vip,
Normal
}

class Customer
{
public Customer(string name, CustomerKind kind)
{
m_Name = name;
m_Kind = kind;
}

private string m_Name;
public string Name
{
get { return m_Name; }
}

private CustomerKind m_Kind;
public CustomerKind Kind
{
get { return m_Kind; }
}

public override string ToString()
{
return "Name: " + m_Name + "[" + m_Kind.ToString() + "]";
}
}

但是,当枚举和条件判断代码的这种组合阻碍您进行更灵活的扩展并可能导致将来维护成本增加时,您可以将其替换为多态性,使用Replace Conditional with Polymorphism重构代码。有关多态性的详细介绍,请参阅文章“你今天是多态的吗?

Comments:

readonly field准确地说,它应该被翻译为“只读域”。在这里,它用于统一翻译语言和const两者修改的量称为“常数”,希望没有误解。


C# FAQ: const和static readonly有什么区别?
我们都知道,const和static readonly它非常相似:通过类名而不是对象名访问,在程序中只读,等等。在大多数情况下,它可以混合使用。
两者的本质区别在于,const的值是在编译期间确定的,因此其值只能在声明时通过常量表达式指定。和static readonly它的值是在运行时计算的,因此也可以通过静态构造函数分配。
一旦我们理解了这个本质区别,就不难看出下面的陈述static readonly和const我们可以交换它吗

  1. static readonly MyClass myins = new MyClass();
  2. static readonly MyClass myins = null;
  3. static readonly A = B * 20;
    static readonly B = 10;
  4. static readonly int [] constIntArray = new int[] {1, 2, 3};
  5. void SomeFunction()
    {
    const int a = 10;
    ...
    }

1:不能替换为const。new运算符需要执行构造函数,因此在编译过程中无法确定
2:可以替换为const。我们也看到,Reference类型的常量(除了String)只能是Null。
3:可以替换为const。我们可以在编译过程中清楚地声明,A等于200。
4:不能替换为const。道理和1是一样的,虽然看起来像1,2,3的数组确实是一个常数。
5:不能替换为readonly,readonly只能用于修改类field不能修改局部变量,也不能修改property其他类成员。

因此,对于那些本质上是常量但不能使用的const要声明,您可以使用static readonly。例如C#规范中给出的示例:

public class Color
{
public static readonly Color Black = new Color(0, 0, 0);
public static readonly Color White = new Color(255, 255, 255);
public static readonly Color Red = new Color(255, 0, 0);
public static readonly Color Green = new Color(0, 255, 0);
public static readonly Color Blue = new Color(0, 0, 255);static readonly需要注意的一个问题是,对于static readonly的Reference类型仅有限,不能分配(写入)。其成员的读写仍然不受限制。

public static readonly MyClass myins = new MyClass();

myins.SomeProperty = 10;  //正常
myins = new MyClass();    //错误,对象是只读的

但是,如果上例中的MyClass不是一个class而是一个struct然后两个后续语句都会出错。

private byte red, green, blue;

public Color(byte r, byte g, byte b)
{
red = r;
green = g;
blue = b;
}
}

using System;

namespace ConstantLab
{
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());

Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
}

class Constant
{
public Constant(int instantReadonlyInt)
{
InstantReadonlyInt = instantReadonlyInt;
}

public const int ConstInt = 0;

public readonly int ReadonlyInt = 1;

public readonly int InstantReadonlyInt;

public static readonly int StaticReadonlyInt = 4;
}
}

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除