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:
一般情況下,如果你需要聲明的常量是普遍公認的並作為單個使用,例如圓周率,黃金分割比例等。你可以考慮使用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的實例對象