C++中提供了const特性,使用該特性定義的參數,其所引用的參數或對象將不會被調用函數修改(當然const還提供了更多的特性,參見“Const正確性”)。在新的建議中,C#也將提供類似的特性。
只讀ref參數
在C#中,“只讀引用”也可稱為“in參數”,兩者提供了類似的限制。只讀引用的基本思想是,如果用“readonly ref”或僅是“in”標注一個參數,那么編譯器會解釋為“將該參數按引用傳遞以改進性能,但不允許實際更改該參數”。該特性對于在高性能場景下的大型結構體尤為有用。在建議中引用了如下的例子:
我們知道,在XNA等圖形庫中的向量/矩陣數學運算符是具有ref操作符的,這純粹是出于性能上的考慮。Roslyn編譯器本身就有代碼使用了結構體,以避免內存分配,并可通過引用的方式傳遞結構體,免除復制的繁瑣。
該語法還結合了C++版本的const。即參數本身是不可更改的,參數所引用的對象或結構體中的所有數據也是不可更改的。
當前在通過引用傳遞一個參數時,必須使用“ref”或“out”關鍵字。在這個建議實現后,使用“in”參數無需受此限制。進而表達式的結果也可以傳遞進來(當前這在VB中是允許的,但是在C#中尚不允許)。
實現細節
對比“ref”和“out”參數,它們的重載規則具有相同的工作機制。
這依然有待議定,但是當前的計劃是不允許“in”參數被匿名函數或async函數捕獲(即生成一個閉包對象)。捕獲“in”參數的問題在于會導致一次拷貝,這破壞了使用“in”參數的初衷,即避免拷貝所導致的性能損耗。
將參數標為“readonly ref”或“in”,并不會令引用值成為不可變值。雖然參數值無法被聲明函數更改,但可以在其它地方修改。無需使用多線程,只需能訪問參數所引用原始變量的方法即可。
在結構體上調用方法可能會導致問題。在建議中是這樣說的:
結構體的所有常規實例方法可轉變(mutate)實例,或是對實例暴露引用(ref-expose),因此必須要創建一個臨時拷貝,正如當前對接收者是只讀域時的做法。
但是,由于沒有考慮向后兼容,也不存在變通方案,所以編譯器只是給出一個警告,以確保用戶留意到這一隱式拷貝。
使用“out”參數時,一個特殊的參數標識了是否需要“in”參數。該參數將被舊的編譯器忽略,因此沒有向后兼容的問題。
只讀ref返回
與該特性密切相關的是將ref returns標為只讀的功能。開發人員使用“in”參數,主要因為它能提供良好的性能,但是“in”參數不允許返回表達式的結果。返回值必須是正常ref返回的一個合法變量,其中可以包含數組元素、ref參數和對象中的域。
ref/in的擴展方法
“ref”擴展方法將允許擴展方法修改傳遞進來的結構體。需要編譯器能驗證傳遞給ref擴展方法的參數是可變的。
雖然“in”擴展方法不允許修改參數,但對于性能敏感的代碼依然十分有用,尤其是結構體非常大的時候。這時當然不需要一個可變參數。
在上面兩種情況下,該特性只能用于結構體。
編輯按:假定廣泛使用了PureAttribute,編譯器將不允許對“in”擴展方法調用非PureAttribute的方法。但由于對性能并無太大改善,因此不大可能廣泛應用。
只讀結構體
將結構體變量標為readonly可能會對性能產生影響。編譯器無法確定某個調用是否會對結構體產生改動,因此會默認能夠修改,并始終復制只讀結構體變量的副本。
使用該特性,開發人員可以在類型層面上將整個結構體標為只讀。這樣一來,編譯器就知道:在通過只讀結構體的變量暴露該只讀結構體時,不需要進行拷貝。
在建議中指出:
唯一一個顯而易見的問題在于:是否需要將其中一些方法改為賦值(mutator)方法。
目前,對只讀結構體進行逐個控制只會增加不必要的復雜性,我們可以后期需要時再行添加。
當前,我們假定可變和不可變成員“混合”的結構體并不常見。此外,可變結構體的部分變量通常需要是LValues,從而避免隱式拷貝的影響。
缺點
建議還指出,這些功能對已有代碼不大可能有幫助,但在如下新場景中會很有用,例如:
在計算能力關乎費用,響應能力關乎競爭優勢的云場景或數據中心場景下。 對延遲有軟性實時需求的游戲/VR/AR場景。在建議中也提出了警告,在“in”參數上的限制可循環作用于被調用函數。但這一問題并不嚴重,因為已經可以使用“out”參數執行同一操作了。
查看英文原文: C# Futures: Read-Only References and Structs