原則二:使用常量時,盡量使用readonly
而不是const
c#
有兩種不同的常量:編譯時常量(
const
)和執行時常量(
readonly
)。它們擁有非常不同的行為,不恰當的使用會造成你程式效能上和正確性上的損失。這兩個方面的損失都不好,但是如果非要選一方的話,乙個慢一點但是正確的程式要好過乙個快一些但錯誤的程式。因為這個原因,比起編譯時常量,你應該更喜歡執行時常量。編譯時常量與執行時常量比起來會略微快一些,但是在靈活性上要差得多。相反的,執行時常量在效能上會差一些,但是能保證常量的值在不同的發布版本中不會改變。
我們用readonly
關鍵字定義執行時常量,用
const
關鍵字定義編譯時常量:
//編譯時常量
publicconstintmillennium=2000;//
執行時常量
publicstatic
readonlyintthisyear=2011;//
注原文是
2004
上面這段**展示了兩種常量在類或結構體範圍中是如何定義的。編譯時常量可以定義在方法中,但是執行時常量就不能。
它們之間行為上的差別主要是由它們不同的訪問方式而產生的。編譯時常量在編譯的時候會被它代表的值代替,如下:
if(
mydatetime.year==millennium)
會與如下**編譯成同樣的中間**:
if(mydatetime==2000
)而執行時常量的值則是在執行的時候才能確定。乙個執行時常量產生的中間**是指向乙個
readonly
變數,而不是乙個具體的值。當你使用這兩種常量的時候,這個區別就產生了一些不同的限制。編譯時常量只能應用在原始資料型別(內建整性或浮點型)、列舉型別(
enum
)、或string
型別。只有這些型別才能夠使你在初始化的時候給常量賦予有意義的值。也只有這些原始型別才能夠在編譯成中間**的時候用字面上的值來替代常量。下面這段**是不能通過編譯的。你不能用
new操作符來初始化乙個編譯時常量,即便它的型別是值型別:
//無法通過編譯,可以用
readonly
替代publicconst datetime classcreation =newdatetime(
2000,1
,1,0
,0,0
);編譯時常量的使用被限定在數字和
string
中。readonly
值一樣也是常量,它們的值在建構函式執行後就不能再被修改了。但
readonly
的不同之處在於它的值是在執行時才被分配的。所以你在使用執行時常量的時候會有更大的靈活性。例如,執行時常量可以是任何型別的。你要在建構函式中對它進行初始化,或者你也可以使用初始化函式來做相初始化。你可以用
readonly
來定義乙個
datetime
結構體常量,但是用
const
卻不行。
如果在類的例項中使用
readonly
值,你可以為乙個類的每乙個例項設定不同的資料值。而如果用編譯時常量,只能通過定義靜態常量。
最重要的區別是
readonly
的值是在執行時才被實際賦值的。在中間**中,當你指向乙個
readonly
常量的時候,它指向的是乙個
readonly
變數,而不是它的具體的值。隨著時間的推移,這個特點在日後的維護中會顯得越來越重要。編譯時常量與你直接使用數字常量所產生的中間**是一樣的。即使是在跨程式集也是這樣的:乙個程式集中定義的常量在另乙個使用到它的程式集中仍然是被同樣的值所替代。
編譯時常量與執行時常量值繫結方式的不同,會影響到它們執行時候的相容性。假設你在名為
infrastructure
的程式集中同時定義了
const
和readonly
常量:publicclass usefulvalues
在另乙個程式集中,你使用到了這些值:
publicvoidprint()
",i);}}
如果你跑一下程式,你將看到如下輸出:
valueis5
valueis6
……valueis9
隨著時間的流逝,你發布了乙個新版本的
infrastructure
程式集,它做了如下修改:
public
class usefulvalues
你發布了新的
infrastructure
程式集,而沒有重新生成用到這些資料的應用程式集。你希望得到如下結果:
valueis105
valueis106
……valueis109
實際上,你根本不會得到任何輸出結果。這個迴圈使用
105作為它的開始值,而用
10作為它的終結條件。
c#編譯器使用常量值
10放入到應用程式的程式集中,而不是使用乙個指向
endvalue
記憶體的指標。再看看
startvalue
,它被定義為
readonly
:在執行時才真正獲得數值。所以,用到這些資料的應用程式集在不重新編譯的情況下也可以獲取到新的數值,簡單地安裝新版本的
infrastructure
程式集,已經足夠去改變所有用到這些資料的客戶端的行為。更新公有的
const
常量的值應該被視為是介面的改變,你必須重新編譯所有用到這個變數的**。而更新
readonly
常量的值則是乙個實施的改變,它的二進位制**與現有的客戶端**是相容的。
從另一方面來說,有時候你確實是想要一些在編譯時就確定值的東西。例如,一組用來標記乙個物件的序列化版本的常量。標記具體版本(歷史版本)的永久的值必須是乙個編譯時常量,他們不會發生改變。而目前版本則必須是乙個執行時常量,它會隨著每次新版本的發行而改變。
private
constintversion1_0=0x0100
;privateconstintversion1_1=0x0101
;privateconstintversion1_2=0x0102;//
主版本發行
privateconstintversion2_0=0x0200
;privatestatic
readonlyintcurrentversion=version2_0;
在每乙個執行檔案中,你都用執行時版本來儲存目前版本號:
//讀取儲存內容,將儲存版本與編譯常量進行比較
protectedmytype(serializationinfo info,streamingcontext cntxt)}//
寫入目前版本
[securitypermissionattribute(serityaction.demand,serializationformater =true)]
void iserializable.getobjectdata(serializationinfo info,streamingcontext cntxt)
const
對比readonly
的最後乙個優點是效能:乙個是訪問常量值,乙個是對
readonly
變數的訪問,前者能產生稍微高效一點的**。儘管如此,這種效能上的改善只是很小的,但是靈活性卻會變得更差,這是需要我們衡量的。如果你要獲取這種效能的改善,就必須放棄靈活性。
當你用到必須和可選引數的時候,你將會遇到很多類似的在執行時還是編譯時對常量值進行處理的抉擇。在呼叫端,可選引數的預設值的設定與編譯時常量的預設值設定一樣。與處理
readonly
和const
類似,你在改變可選引數的時候要非常小心(參見原則十,譯註:目前未翻譯,呵呵!先打個標!)。
當值必須在編譯時確定的時候你只能使用
const
:如屬性引數和列舉型別(
enum
)定義。那些在不同發布版本間定義的值不會發生改變的,也可以用
const
定義。除此之外,最好還是使用增加靈活性的
readonly
常量吧!
總結:第
2節的翻譯總算是快了一點。還是比較吃力,不過感覺會比第
1節要好一些了。接下來的乙個多星期會比較忙,估計第
3節的翻譯要過段時間才能出來了。見諒見諒!
Effective Java 中文第二版
第2章 建立和銷毀物件 第1條 考慮用靜態工廠方法代替構造器 第2條 遇到多個構造器引數時要考慮用構建器 第3條 用私有構造器或者列舉型別強化singleton屬性 第4條 通過私有構造器強化不可例項化的能力 第5條 避免建立不必要的物件 第6條 消除過期的物件引用 第7條 避免使用終結方法 第3章...
Effective Java 中文第二版
第2章 建立和銷毀物件 第1條 考慮用靜態工廠方法代替構造器 第2條 遇到多個構造器引數時要考慮用構建器 第3條 用私有構造器或者列舉型別強化singleton屬性 第4條 通過私有構造器強化不可例項化的能力 第5條 避免建立不必要的物件 第6條 消除過期的物件引用 第7條 避免使用終結方法 第3章...
array c 實現,第二版
include include include include using namespace std namespace mylib array const array coll alloc coll.alloc size coll.size ia null array const pointer...