C#基礎:基於const與readonly的深入研究

來源:互聯網
上載者:User

•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:

複製代碼 代碼如下:Code
using System;
publicclass Order
{
public Order()
{
Guid guid = Guid.NewGuid();
ID = guid.ToString("D");
}
// 對於每一份訂單,其訂單序號都是即時確定的常量。
style="color: #008000;">
publicreadonlystring ID;
publicoverridestring ToString()
{
return"Order ID: "+ ID;
}
}

Explaintion:
•如果結合資料庫使用,ID field通常都會都會與某個表的主健(primary key)關聯起來,如Orders表的OrderID。
•資料庫的主健通常採用以下三種方式:
•自動遞增值。你可以通過把DataColumn.AutoIncrement設定為true值來啟用自動遞增特性。
•唯一名稱。這個是使用自己定義的演算法來產生一個唯一序號。
•GUID(通用唯一識別碼)。你可以通過System.Guid結構來產生GUID,如上例。複製代碼 代碼如下:Code
using System;
class Customer
{
public Customer(string name, int kind)
{
m_Name = name;
m_Kind = kind;
}
publicconstint NORMAL =0;
publicconstint VIP =1;
publicconstint SUPER_VIP =2;
privatestring m_Name;
publicstring Name
{
get { return m_Name; }
}
privatereadonlyint m_Kind;
publicint Kind
{
get { return m_Kind; }
}
publicoverridestring ToString()
{
if(m_Kind == SUPER_VIP)
return"Name: "+ m_Name +"[SuperVip]";
elseif(m_Kind == VIP)
return"Name: "+ m_Name +"[Vip]";
else
return"Name: "+ m_Name +"[Normal]";
}
}

•一般情況下,如果你需要聲明的常量是普遍公認的並作為單個使用,例如圓周率,黃金分割比例等。你可以考慮使用const常量,如:public const double PI = 3.1415926;。如果你需要聲明常量,不過這個常量會隨著實際的運行情況而決定,那麼,readonly常量將會是一個不錯的選擇,例如上面第一個例子的訂單號Order.ID。
•另外,如果要表示對象內部的預設值的話,而這類值通常是常量性質的,那麼也可以考慮const。更多時候我們對原始碼進行重構時(使用Replace Magic Number with Symbolic Constant),要去除魔數(Magic Number)的影響都會藉助於const的這種特性。
•對於readonly和const所修飾的變數究竟是屬於類層級的還是執行個體對象層級的問題,我們先看看如下代碼:複製代碼 代碼如下:Code
using System;
namespace ConstantLab
{
class Program
{
staticvoid 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;
}
publicconstint ConstInt =0;
publicreadonlyint ReadonlyInt =1;
publicreadonlyint InstantReadonlyInt;
publicstaticreadonlyint StaticReadonlyInt =4;
}
}

•使用Visual C#在Main()裡面使用IntelliSence插入Constant的相關field的時候,發現ReadonlyInt和InstantReadonlyInt需要指定Constant的執行個體對象;而ConstInt和StaticReadonlyInt卻要指定Constant class(參見上面代碼)。可見,用const或者static readonly修飾的常量是屬於類層級的;而readonly修飾的,無論是直接通過賦值來初始化或者在執行個體建構函式裡初始化,都屬於執行個體對象層級。
•一般情況下,如果你需要表達一組相關的編譯時間確定常量,你可以考慮使用枚舉類型(enum),而不是把多個const常量直接嵌入到class中作為field,不過這兩種方式沒有絕對的孰優孰劣之分。複製代碼 代碼如下:Code
using System;
enum CustomerKind
{
SuperVip,
Vip,
Normal
}
class Customer
{
public Customer(string name, CustomerKind kind)
{
m_Name = name;
m_Kind = kind;
}
privatestring m_Name;
publicstring Name
{
get { return m_Name; }
}
private CustomerKind m_Kind;
public CustomerKind Kind
{
get { return m_Kind; }
}
publicoverridestring ToString()
{
return"Name: "+ m_Name +"["+ m_Kind.ToString() +"]";
}
}

然而,當這種結合使用枚舉和條件判斷的代碼阻礙了你進行更靈活的擴充,並有可能導致日後的維護成本增加,你可以代之以多態,使用Replace Conditional with Polymorphism來對代碼進行重構.
Comments:
•readonly field準確來說應該翻譯成為“唯讀域”,這裡是為了統一翻譯用語才將它和const兩者所修飾的量都說成“常量”,希望沒有引起誤會。

工作原理
readonly為運行時常量,程式運行時進行賦值,賦值完成後便無法更改,因此也有人稱其為唯讀變數。
const為編譯時間常量,程式編譯時間將對常量值進行解析,並將所有常量引用替換為相應值。
下面聲明兩個常量:複製代碼 代碼如下:public static readonly int A = 2; //A為運行時常量
public const int B = 3; //B為編譯時間常量

下面的運算式:
int C = A + B;
經過編譯後與下面的形式等價:
int C = A + 3;
可以看到,其中的const常量B被替換成字面量3,而readonly常量A則保持引用方式。
聲明及初始化
readonly常量只能聲明為類欄位,支援執行個體類型或靜態類型,可以在聲明的同時初始化或者在建構函式中進行初始化,初始化完成後便無法更改。
const常量除了可以聲明為類欄位之外,還可以聲明為方法中的局部常量,預設為靜態類型(無需用static修飾,否則將導致編譯錯誤),但必須在聲明的同時完成初始化。
資料類型支援
由於const常量在編譯時間將被替換為字面量,使得其取實值型別受到了一定限制。const常量只能被賦予數字(整數、浮點數)、字串以及枚舉類型。下面的代碼無法通過編譯:複製代碼 代碼如下:public const DateTime D = DateTime.MinValue;

改成readonly就可以正常編譯:複製代碼 代碼如下:public readonly DateTime D = DateTime.MinValue;

可維護性
readonly以引用方式進行工作,某個常量更新後,所有引用該常量的地方均能得到更新後的值。
const的情況要稍稍複雜些,特別是跨程式集調用:複製代碼 代碼如下:public class Class1
{
public static readonly int A = 2; //A為運行時常量
public const int B = 3; //B為編譯時間常量
}
public class Class2
{
public static int C = Class1.A + Class1.B; //變數C的值為A、B之和
}
Console.WriteLine(Class2.C); //輸出"5"

假設Class1與Class2位於兩個不同的程式集,現在更改Class1中的常量值:複製代碼 代碼如下:public class Class1
{
public static readonly int A = 4; //A為運行時常量
public const int B = 5; //B為編譯時間常量
}

編譯Class1並部署(注意:這時並沒有重新編譯Class2),再次查看變數C的值:複製代碼 代碼如下:Console.WriteLine(Class2.C); //輸出"7"

結果可能有點出乎意料,讓我們來仔細觀察變數C的賦值運算式:複製代碼 代碼如下:public static int C = Class1.A + Class1.B;

編譯後與下面的形式等價:複製代碼 代碼如下:public static int C = Class1.A + 3;

因此不管常量B的值如何變,對最終結果都不會產生影響。雖說重新編譯Class2即可解決這個問題,但至少讓我們看到了const可能帶來的維護問題。
效能比較
const直接以字面量形式參與運算,效能要略高於readonly,但對於一般應用而言,這種效能上的差別可以說是微乎其微。
適用情境
在下面兩種情況下:
a.取值永久不變(比如圓周率、一天包含的小時數、地球的半徑等)
b.對程式效能要求非常苛刻
可以使用const常量,除此之外的其他情況都應該優先採用readonly常量。
儘管你寫了很多年的C#的代碼,但是可能當別人問到你const與readonly的區別時候,還是會小小的愣一會吧~
筆者也是在看歐立奇版的《.Net 程式員面試寶典》的時候,才發現自己長久以來竟然在弄不清出兩者的情況下,混用了這麼長的時間。的確,const與readonly 很像,都是將變數聲明為唯讀,且在變數初始化後就不可改寫。那麼,const與readonly 這兩個修飾符到底區別在什麼地方呢?其實,這個牽扯出C#語言中兩種不同的常量類型:靜態常量(compile-time constants)和動態常量(runtime constants)。這兩者具有不同的特性,錯誤的使用不僅會損失效率,而且還會造成錯誤。
首先先解釋下什麼是靜態常量以及什麼是動態常量。靜態常量是指編譯器在編譯時間候會對常量進行解析,並將常量的值替換成初始化的那個值。而動態常量的值則是在啟動並執行那一刻才獲得的,編譯器編譯期間將其標示為唯讀常量,而不用常量的值代替,這樣動態常量不必在聲明的時候就初始化,而可以延遲到建構函式中初始化。
當你大致瞭解上面的兩個概念的時候,那麼就可以來說明const與readonly了。const修飾的常量是上述中的第一種,即靜態常量;而readonly則是第二種,即動態常量。那麼區別可以通過靜態常量與動態常量的特性來說明:
1)const修飾的常量在聲明的時候必須初始化;readonly修飾的常量則可以延遲到建構函式初始化
2)const修飾的常量在編譯期間就被解析,即常量值被替換成初始化的值;readonly修飾的常量則延遲到啟動並執行時候
此外const常量既可以聲明在類中也可以在函數體內,但是static readonly常量只能聲明在類中。
可能通過上述純概念性的講解,對有些初學者有些暈乎。下面就一些例子來說明下: 複製代碼 代碼如下:using System;
class P
{
static readonly int A=B*10;
static readonly int B=10;
public static void Main(string[] args)
{
Console.WriteLine("A is {0},B is {1} ",A,B);
}
}

對於上述代碼,輸出結果是多少?很多人會認為是A is 100,B is 10吧!其實,正確的輸出結果是A is 0,B is 10。好吧,如果改成下面的話:複製代碼 代碼如下:using System;
class P
{
const int A=B*10;
const int B=10;
public static void Main(string[] args)
{
Console.WriteLine("A is {0},B is {1} ",A,B);
}
}

對於上述代碼,輸出結果又是多少呢?難道是A is 0,B is 10?其實又錯了,這次正確的輸出結果是A is 100,B is 10。
那麼為什麼是這樣的呢?其實在上面說了,const是靜態常量,所以在編譯的時候就將A與B的值確定下來了(即B變數時10,而A=B*10=10*10=100),那麼Main函數中的輸出當然是A is 100,B is 10啦。而static readonly則是動態常量,變數的值在編譯期間不予以解析,所以開始都是預設值,像A與B都是int類型,故都是0。而在程式執行到A=B*10;所以A=0*10=0,程式接著執行到B=10這句時候,才會真正的B的初值10賦給B。如果,你還是不大清楚的話,我們可以藉助於微軟提供的ILDASM工具,只需在Vs 2008 Command下輸入ILDASM就可以開啟,如下所示:

分別開啟上述兩個代碼編譯後產生的可執行檔,如所示:


static readonly可執行程式的結構

const

可執行程式的結構

在上述兩張圖中都可以看到A與B常量,分別雙擊節點可以看出其中的差異:

static readonly修飾的常量A

const修飾的常量A

static readonly修飾的常量B

const修飾的常量B
從中可以看出,const修飾的常量在編譯期間便已將A,B的字面值算出來了,而static readonly修飾的常量則未解析,所以在Main函數中有以下的區別:

static readonly程式的Main函數

const程式的Main函數

從Main函數中我們可以看出,const的那個程式的輸出直接是100與10,而readonly在輸出的時候確實P::A與P::B,即將A與B常量的值延遲到啟動並執行時候才去確定,故輸出是0與10。
那麼對於靜態常量以及動態常量還有什麼特性呢?其實,靜態常量只能被聲明為簡單的資料類型(int以及浮點型)、枚舉、布爾或者字串型,而動態常量則除了這些類型,還可以修飾一些物件類型。如DateTime類型,如下:
//錯誤
const DateTime time=new DateTime();
//正確
static readonly DateTime time=new DateTime();
上述錯誤在於不能使用new關鍵字初始化一個靜態常量,即便是一個實值型別,因為new將會導致到運行時才能確定值,與靜態變數編譯時間就確定字面值有悖。
歐書上最後給出了對靜態常量與動態常量之間的比較,如下表所示:

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.