***所以說啊常數是什麼***
依據 C#程式設計手冊,常數(Constants)的定義為 : Constants are immutable values which are known at compile time and do not change for the life of the program.
常數是在編譯時期就已經知道的,一個不會改變的數值,並且在程式的生命週期中都不允許修改。(不允許修改很重要,請抄寫50次)
且常數可以為特別的數值提供有意義的名稱,而非Magic Number ,影響程式的可讀性
在學程式時,常被用來舉例的就是 圓 周 率。每個人國中小絕對都學過,3.1415926..........
大家都知道,圓周率是不會變的,但如果被不知情的( ? )其他工程師改了,那真的是欲哭無淚,覺得不知道是誰在整自己~
這種類型(恆不變的數值),我們就會宣告為常數~
一開始寫Java的時候,常數很單純,final 就對了 ( 後來聽說還有另一個 )
開始寫C#之後,卻有點懷疑人參~
( 這次寫的內容有點長,急著找答案的人歡迎看懶人包 : 點我 )
***C#的const及readonly***
在Google搜尋「C# 常數」,其實十有八九出現的還是const
但夜路走多了,會發現其實還有另一個牽扯不清的關鍵字 : readonly
在Google搜尋的話,兩者因其不同的特性,可以找到很多不同的稱呼
const : Constants常數,稱之為「靜態常數」,或稱「編譯階段常數」
在編譯階段即解析 常數運算式 , 並將其值指派給所宣告的常數。
readonly : 字面意思「唯讀」,又稱「動態常數」,或稱「執行階段常數」
在執行時才獲得實際的數值。
到底實際上有什麼差別? 讓我們看下去~
***先來談談const ( Constants ) ***
**const的宣告**
使用const關鍵字,只有C#內建型別,如數值、布林、字串或是Null參考可以被指定為常數。
Null參考是指說,如果const的類型是參考型別,那只能指定其為Null。
關於為什麼 const 在參考型別只能指派為 null , 這點有不少討論文章。主要是因為對 const 來說,其等號右方只能指定為常數。
至於到底有沒有作用,則是見仁見智,有些說法覺得指派 const 為 null ,也可以是增加可讀性的一種。
而在生命週期部分,const可用來宣告 常數欄位 或 區域常數 ,且在宣告時就必須指派其數值,否則會出現錯誤:「需要為const欄位提供值」
且如前面所提,因const為靜態常數 無法被指派 非常數運算式,常數運算式是在編譯時期即可評估。
上面的說法感覺很難理解 XD 可以參考下圖 : 無法指派Math.Sin的數值給const,因其是在執行階段運算,故出現錯誤。
如果在編譯階段,試圖修改const的值,會出現錯誤:「指派的左側必須是變數、屬性或索引子。」
**命名規則**
在開發的潛規則中,還是會比較常聽到建議 const常數 在命名時 使用全大寫Upper Case,並在每個單字間以 _ ( underscore character ) 分隔,與一般變數、屬性作區別。
不過這還是以團隊開發規範為主,畢竟每個人見解還是不太相同。
**存取修飾詞**
可標記 存取修飾詞(Access modifiers, 如 : public private internal... aso),來定義如何存取常數。
**Static靜態化**
一個 const 常數成員,即被編輯器視為static,其數值在所有實體都相同。
因此 const 不需要標為 static,會出現錯誤:「常數不可標記為static」。
常數的存取方式與 static 欄位相同 : 若不是在定義常數的類別中欲呼叫該常數,則使用類別名稱做為前置、句號(a period)及常數名稱的格式來存取。
**特色**
事實上,編譯器在C#中遇到 constant 識別碼時(例如 : January),會在轉為中繼語言(Intermediate Language, IL)時替代為字面數值。
故在執行階段並沒有變數位址關連到常數。
也因const常數沒有占用記憶體,效能較快,但無法以址傳遞,並且無法作為表示式(Expression)的左值 ( 無法放在 指派運算子= 的 左方 )。
但也因為 const 在中繼語言會轉為字面數值,如果有參考到定義在其他程式碼 ( 如 : dll ) 的常數,當該 dll 有修改常數的數值,你的程式仍會持有(Hold)原本的字面常數,直到妳重新編譯。
網路上大部分不建議使用 const , 也是因為此點。
(請原諒不專業寫手的圖畫得很爛www)
***那麼readonly呢?***
**readonly的宣告**
相較於const, readonly的規則少很多~
基本型別以至於類別、結構或陣列等等,都可以使用 readonly 修飾詞。同樣的,readonly可指派 非常數運算式。
但無法宣告為區域變數在方法中使用,會顯示 : 「修飾元'readonly'對此項目無效」
可宣告為 readonly 的型別種類繁多、族繁不及備載。相對的就不是那麼單純,藏著一點小小的秘密。
對於 readonly基本型別 和 readonly參考型別,是有一個很大的差異的 : 「參考型別是記錄其存放數值的記憶體位置,如果一個參考型別欄位是readonly,則其必定參考同一物件。
此物件不可重新指派,readonly修飾詞會預防其被不同的物件取代。然後,readonly修飾詞不會防止物件中的資料欄位被修改。」
看起來很長很饒舌,其實就一句話 : 宣告為 readonly 的物件無法重新指派記憶體位置,但該類別的屬性及欄位,不會受物件的 readonly 影響,仍可以修改。
另外相較於 const 而言比較特別的是,readonly 欄位可在 宣告 或 建構函式 中賦值。 這是什麼意思呢?
就是說,除了在一開始宣告時指派值之外,我們也可以選擇保留到 建構式 Constructor 中 再指派值。(不知道建構式是什麼的快去查~~~)
因此,readonly 欄位可能會因使用的建構函式而有不同的值( 不像 const 須在一開始宣告時即指定 ),並且之後無法被修改。
By the way,在 Constructor 中 可以不受限的重新指派 readonly 欄位。(下面再加一行 CONST_CODE = 5 也沒有問題)
在離開建構子之後就無法再被指派。如果編譯時,在一般函式中修改readonly欄位數值,會出現錯誤:「無法予與指派值的唯讀欄位」
**關於CA2104**
其實這只是在Microsoft Docs中看到的一個無聊小概念,而且也已經過時了。沒有興趣的朋友可以直接跳到下一小段 : 傳送門
在 Microsoft Docs 中的 readonly 說明提到一個Warning CA2104 : 「An externally visible type that contains an externally visible read-only field that is a mutable reference type may be a security vulnerability and may trigger warning CA2104 : "Do not declare read only mutable reference types."」
好,看得一頭霧水。那我們看看中文版本的Microsoft Docs : 「外部可見的類型(包含可變動參考型別的外部可見唯讀欄位)可能是安全性弱點,可能會觸發警告 CA2104 :「不要宣告唯讀的可變動參考型別」。」
嗯,好。阿鬼你還是說英文吧 ( ? ) XDDD
總之多翻了幾篇後,可以發現 : 「此警告規則 CA2104 已過時,在VS2017後的版本含2019都已移除。」
但基於好奇還是多看了一下,找到一段範例碼如下
StringBuild 為可變動參考類型,其為外部可見唯讀欄位(public readonly)。而 string 是 不可變參考型別,即具現化後其值永遠不會變更,
作為 readonly 的 StringBuilder 不但可以重新指派,還可以修改其值。
真是讓我受了大大的驚嚇QQ
好像目前透過某種設定,還是可以出現警告 (畢竟太嚇人) ,不過暫時就先至此,沒深入查看下去了~
(沒事還是別這樣寫嚇人XD 知道一下就好)
在其他文章,常會看到 readonly 與 static 搭配使用。
是的沒錯,readonly 並非預設 static,如欲使用請自行加上。
**特色**
前面有提到,const 沒有耗用記憶體,因此有較佳的效能。既然如此,有什麼理由要用 readonly ?
readonly 因為要保存常數,所以有耗用記憶體,但也因此有較好的彈性、使用上較靈活。
靈活? 怎麼樣的靈活? 他可以旋轉360度嗎? ( 大誤
記得前面講的,當引用來自其他的程式庫(dll) 的 const 常數時,因其在 中繼語言 被轉為 字面數值,使引用方的 dll 也需要重新建置,才能取得更改後的數值。
但如果是 readonly 常數,則引用方的 dll 就不需要重新建置。你問說為什麼? 因為 readonly 所記憶的是路徑,而不是字面數值啊~!
***剛好看到的某個範例***
另外在數篇文章海中,有篇還真的是長了見識,也或是我太孤陋寡聞。 XDD
有寫過程式的人應該都知道,通常我們必須先宣告一個變數,之後才能使用,否則編譯階段會出現錯誤。
之前剛接觸 JavaScript 時,就被 Hoisting 開啟了新的三觀,卻沒想到在C#也可以體驗到 XD
其實在IDE訊息中已經寫得很清楚,中文是 : 「欄位初始設定式無法參考非靜態欄位、方法或屬性」,所以當你加上了static ...
不但可以編譯通過, A 還出乎意料的是0~ 但細想想的話,就會覺得好個不意外
只是在文章中的例子是加上 static readonly, 讓我們來看看結果
跟 沒有加上 readonly 時是一樣的結果
在一些介紹 static readonly 的範例中看到 , 但實際上這個結果跟 readonly 並沒有關係,而是來自於 static 的概念
特此說明~有錯請糾~
***補充 : 看到某個常數的寫法***
Microsoft Dosc : How to define constants in C# 中提到,可以建立一個 名稱為 Contants 的靜態類別,再把常數都放進去
這樣需要以 類別名稱限定詞 呼叫時,可以幫助你跟你的小夥伴們都知道,這類別中的數值都是常數~
***最後的最後***
這篇不知不覺參考資料越看越多、文章越寫越久,大概前前後後搞了一個月有吧。
明明是很簡單的概念,卻藏了很多的細節~
很多文章最後都會建議使用 readonly 而非 const。因為 readonly 的彈性, 可以避免更改其值後,可能相關程式都需要重新發佈的問題。
但以我自己來說,還是會把 readonly 視為唯讀而非常數,畢竟兩者在使用的根本上,有不同的目的。
我還是較傾向一篇國外文章( Const vs Static vs Readonly in C# )的說法 :
如果你確定這個數值永遠、永遠、永遠不會變動 ( 圓周率或是其他理科方面的公式常數 ),那你還是可以使用const。
但如果你不確定這個值會不會變動 ( 公司內部自定義之類 ),那保險起見,你還是用readonly。
小妹才疏學淺,以上內容若有任何錯誤或缺漏的地方,都歡迎大家分享或詢問~
***
另外如果有點進參考資料的話,應該會發現部分文章提及 get & enum
enum 類型可定義 整數內建類型 的具名常數 (例如 int、uint、long 等等)。
get 則是來自於 C# 3.0 的自動實作屬性 (auto implemented properties)
因為此篇已經太長了,連我自己都受不了,就留待之後另外發文了~
(欠文越來越多...)
終於收尾囉~
const | Readonly | |
常數類型 | 靜態常數 | 動態常數 |
指派階段 | 編譯階段常數 | 執行階段常數 |
宣告型別 | C#內建型別 如布林、數值和字串,及null參考 |
基本型別以至於類別、結構或陣列等 |
指派數值 | 宣告時就必須指派其數值 | 可在宣告 或 建構函式中 賦值 |
生命週期 | 可宣告常數欄位或區域常數 | 無法宣告為區域常數 |
靜態類別 | 是,預設static | 否,預設非static |
優 點 | 效能佳 | 有彈性 |
參考資料 :
[C#.NET] 定義常數時用 readonly 好? 還是 const 好?
Constants (C# Programming Guide)
Difference between readonly and const keyword in C#
C# Readonly - 教學筆記 (使用visual studio)
[C#] 006.區別readonly和const的使用方法 (程式有彈性用readonly,要效率用const)
Const vs Static vs Readonly in C#
C#基础知识系列八const和readonly关键字详细介绍
Why are we allowed to use const with reference types if we may only assign null to them?
關於Visual Studio 2008:C#CA2104-自動代碼分析不喜歡靜態唯讀可變類型
What is the difference between const and readonly in C#?
C# naming convention for constants?