C#的未來:不可變變量
英文原文:C# Futures: Immutable Variables
在 C# 中,readonly 關鍵字只能作用于字段級別。而在第 115 條提議 “只讀本地變量與參數”中,將對 readonly 關鍵字進行擴展,以涵蓋更廣泛的場景。
提議中首先提出了創建只讀本地變量的功能。這種功能的第一個使用場景只是用于文檔,通過將某個變量標記為只讀,就意味著在函數中的其它地方不能夠、也不應該改動這個本地變量。對于代碼很長、非常復雜,無法一眼看清所有情況的函數來說,這一點顯得尤為實用。
第二個使用場景是在進行多線程運算和使用閉包時保證安全性。如果你通過執行 Parallel.ForEach 產生了一個閉包,則很容易會導致競態的產生。而如果你默認將所有本地變量標為只讀,那么那些可變的本地變量將顯得很突出,因此在審查中就會注意到它。
語法上的困擾
如果能夠默認使用只讀本地變量,那么它確實能夠帶來許多價值,但這也要求語法不能太過繁重。考慮一下以下代碼:
var gravity = 9.780327; double gravity = 9.780327; const double gravity = 9.780327;
雖然 gravity(重力)理應作為一個常量,但多數開發者都傾向于使用第一種代碼版本。他們之所以沒有選擇“正確的做法”,只是為了簡化輸入,并且在一個獨立的場景中,選用哪種方式其實無關緊要。
這種簡便性勝于正確性的傾向也出現在類型轉換中,下面的代碼有一個常見的錯誤,許多有經驗的程序員也難以避免。
var button = sender as Button; button.Enabled = false;</div>
正確的方法應該是“button = (Button) sender”,但這種正確的代碼在輸入時的復雜性稍高。
為了應對這些困擾,該提議提出了一種隱式類型化本地變量的簡寫方式,目前所考慮的有以下兩種關鍵字:
val gravity = 9.780327; let gravity = 9.780327;</div>
在這兩者之間,目前更傾向于“let”這一寫法,因為在 LINQ 表達式中已經用到過它,并且從視覺角度來看更容易與“var”區別開(對于那些母語中不區分r與l的非英語使用者來說,后者也會更好)。
只讀參數
接下來就是將參數標注為只讀的功能。使用 Visual Studio 代碼分析工具的開發者可能認為這一功能有些多余,因為該工具會自動阻止對常規的參數進行改變。但有些用例是它無法涵蓋的。
在開發注重高性能的代碼時,使用結構體替代類的做法并不罕見,即使結構體有時顯得更大。為了避免結構體復制帶來的消耗,因而會使用 ref 參數的方式將這些結構體傳遞給函數。
從文檔的角度來看,這種函數簽名無法清晰地向調用者表現出該參數不應被修改的意圖。而將參數標注為“readonly ref”這種方式就能夠填補這一漏洞。
并非每個人都喜歡這一語法,因為它要求使用“ref”裝飾調用端,而這會產生某些誤導的傾向。因此 Porges 建議使用一種“in”關鍵字予以代替。
void DoSomething (readonly ref LargeStruct value) DoSomething (ref myLocal); void DoSomething (in LargeStruct value) DoSomething (myLocal);</div>
Readonly 與 Const
雖然 readonly 能夠徹底地避免某個值被替換,但它通常無法避免對某個對象的成員的改動。因此這條提議另外附加了一點,即對 readonly 所提供的保護能力進行擴展。
像 C++ 這樣的語言已經能夠支持這一概念了。雖然它確實能夠像所說的方式一樣工作,但要正確地使用它并不是一件容易的事,因為開發者們經常對于 const 這個關鍵字是對應變量本身,還是對應其中的內容感到困擾。因此在此語法中避免這種含糊性也是同樣重要的。一種建議認為,可以使用“readonly”表示 對變量本身的保護,而使用“const”表示對其內容的保護。
C++中還提供了 const 函數的概念,這種函數只能由定義為 readonly 或 const 的變量進行調用,因為這些變量已經證實了不會改變對象的狀態。在 .NET 中也能夠通過 Pure 屬性實現這一概念,但當前的 C# 編譯器都不支持這一屬性。
腳注:Readonly、結構體與字段
雖然并非提議中的一部分,但也應當考慮 readonly、結構體與字段的交互。考慮以下代碼:
private readonly Foo _foo = new Foo (1, 2, 3);</div>
如果 Foo 類型是完全不可變的,那么 readonly 字段就能夠像預期一樣工作。但如果 Foo 中提供了任何屬性的 setter,那么每次你讀取這個字段時,編譯器會生成一個拷貝,所以你不會在無意中修改了它的內容。因此與 readonly 引用字段不同,readonly 結構體真正做到了只讀。但由于這種方式帶來了隱藏的性能損耗以及不一致性,因此如果能夠以一種更清晰的方式表達出這一概念,將具有很大的益處。
要了解更多信息,請閱讀 Eric Libbert 的帖子“Mutating Readonly Structs”。