探索 C# 12 的 ref readonly 參數(shù)修飾符
在 Visual Studio 17.8 Preview 2 里我們可以使用該特性。該特性允許用戶(hù)使用 ref readonly
修飾符修飾參數(shù),表示該引用只讀。
Part 1 C# 似乎已經(jīng)有這種特性了
我們?cè)谛揎梾?shù)的時(shí)候,我們?nèi)绻獋鬟f一個(gè)引用而不是數(shù)值的話,我們可以采用 ref
修飾符來(lái)修飾它,這樣表示的是該參數(shù)傳入的是這個(gè)變量的地址數(shù)據(jù),而不是內(nèi)部的數(shù)值。
如代碼所示,我們展示的是一個(gè) ref
修飾符修飾過(guò)的參數(shù)的一個(gè)例子??梢钥吹?,它的記號(hào)寫(xiě)法和 C/C++ 里的地址和引用沒(méi)有多少區(qū)別。只是 C# 里把 C++ 的引用記號(hào)改造成 ref
關(guān)鍵字了而已:
以及
不過(guò),C# 如果要限制引用只讀的話,可能只能換個(gè)修飾符了。和 C/C++ 不同,C/C++ 具有 const
修飾符,而 C# 呢?別急。C# 有 in
關(guān)鍵字。
從 C# 7 開(kāi)始,C# 允許使用 in
修飾符替換 ref
,表示變量傳入的是只讀引用。所謂只讀引用,指的是對(duì)象傳入的是它的地址,且該地址內(nèi)的數(shù)據(jù)不能變動(dòng)(你只能讀取里面的數(shù)值來(lái)用,而不能修改它內(nèi)部的數(shù)據(jù))。
ref readonly
是個(gè)什么呢?
Part 2 限制指向的數(shù)值只讀
C# 12 所擁有的新特性 ref readonly
限制的是參數(shù)傳入的這個(gè)對(duì)象內(nèi)部的數(shù)值只讀。啊?這不是跟 in
參數(shù)修飾符的用途完全一樣嗎?這沒(méi)什么區(qū)別啊……
是的,沒(méi)有啥特別大的區(qū)別。要說(shuō)區(qū)別的話,還是有的。下面我們來(lái)說(shuō)下 C# 12 這個(gè) ref readonly
和 in
的區(qū)別。
2-1 參數(shù)值需要用 in
修飾
C# 12 的 ref readonly
修飾符和 in
幾乎一致,因此你寫(xiě)的幾乎所有帶有 in
修飾符的地方全都能替換為 ref readonly
原本的調(diào)用方,in
修飾符修飾的參數(shù),參數(shù)表達(dá)式(數(shù)值等)都不需要使用配套的 in
這一點(diǎn)造成了和 ref
和 out
修飾的參數(shù)不一致的語(yǔ)法:ref
修飾的參數(shù),在調(diào)用的時(shí)候也需要寫(xiě)出來(lái) ref
關(guān)鍵字表達(dá)這里傳入的是引用。微軟這么規(guī)定是為了可讀性和一些編譯器在語(yǔ)法上的解析的便捷。當(dāng)然,out
修飾符也是一樣。調(diào)用方你也需要顯式給出 out
修飾符。但 in
修飾符是一個(gè)例外。你可以調(diào)用的時(shí)候不需要給這個(gè) in
也是 OK 的。當(dāng)然,寫(xiě)出來(lái)也行。
但是,C# 12 的這個(gè)新修飾符 ref readonly
必須要求你顯式寫(xiě)出 in
修飾符,表示這里傳入的是一個(gè)只讀引用。
如果不寫(xiě)會(huì)怎么樣呢?不會(huì)怎么樣,但是你會(huì)收到一個(gè)編譯器警告。告知你這里必須給出 in
修飾符。如果你忽略警告,也沒(méi)有任何問(wèn)題;你在運(yùn)行的時(shí)候程序仍然可以正確得到預(yù)期的結(jié)果。
這種使用編譯器警告代替?zhèn)鹘y(tǒng)的編譯器錯(cuò)誤的操作,實(shí)際上有兩層考慮:
因?yàn)樗?
in
修飾符相似,所以考慮用戶(hù)兼容語(yǔ)法的習(xí)慣,不強(qiáng)制要求添加;因?yàn)榫幾g器即使你不加修飾符
in
也可以正確分析代碼,因?yàn)樗肋@里傳的是只讀引用。
很好。我希望我解釋得足夠清晰了。下面我們來(lái)說(shuō)說(shuō)為什么用的是 in
修飾傳入的參數(shù)表達(dá)式。
按道理說(shuō),因?yàn)槭?ref readonly
,所以按照契合 C# 臨時(shí)變量對(duì)只讀引用的定義用法,右值表達(dá)式(等號(hào)右側(cè)的表達(dá)式)是配合左邊定義變量用的 ref
關(guān)鍵字,所以使用的也是 ref
修飾符:
ref readonly
,但右邊用的是 ref
;而為什么參數(shù)的 ref readonly
修飾符,傳入進(jìn)來(lái)的表達(dá)式卻要求用 in
而不是 ref
呢?
其實(shí)是因?yàn)?ref
在 C# 里固定的是一種傳入引用的意思。雖說(shuō) ref readonly
表達(dá)參數(shù)也是說(shuō)傳引用(雖然是引用的成員只讀),但它更多想要強(qiáng)調(diào)的是數(shù)值上的不可變。和臨時(shí)變量不同,臨時(shí)變量在定義的時(shí)候右值表達(dá)式是期望和等號(hào)左側(cè)聲明達(dá)到一個(gè)契合狀態(tài),所以使用同樣的修飾符可以更加直觀;而在這里用 in
修飾右值表達(dá)式則反而不太能搞清楚它表達(dá)的意思。
所以這實(shí)際上是一種對(duì)語(yǔ)法書(shū)寫(xiě)的習(xí)慣而造成的原因。
2-2 不能傳常量
這是最為致命的一個(gè)問(wèn)題。C# 7 誕生的 in
修飾符表示的是參數(shù)的只讀;不過(guò),這并未限制一些特有的看似 bug 的特殊情況——常量。
常量我們都知道,它必然是只讀的。而 in
int
而反倒去傳引用還讓對(duì)象只讀呢?
C# 主要是為了解決這種操作,所以才使用新概念 ref readonly
來(lái)代替原有的 in
修飾符。它的作用主要是限制常量的使用。顯然用戶(hù)很少會(huì)在常量上面使用只讀引用的傳遞,所以這種操作對(duì)用戶(hù)層面而言幾乎不會(huì)存在;但從另外一個(gè)角度來(lái)說(shuō),微軟定義 C# 的其中一個(gè)主要特征就是兼容性。早期版本帶來(lái)的陋習(xí)你可以不用,但微軟不會(huì)刪掉它;怕的是用戶(hù)在使用新版本語(yǔ)法的時(shí)候,舊版本不可用進(jìn)而導(dǎo)致的新版本編譯失敗的問(wèn)題。
這就是為什么微軟會(huì)強(qiáng)行搞出一個(gè)新的概念來(lái)避免改動(dòng) in
的語(yǔ)義。而有了 ref readonly
之后,42 因?yàn)槭浅A克晕覀儫o(wú)法取該對(duì)象的地址,因而無(wú)法對(duì)其使用引用,這便是 ref readonly
修飾符和 in
修飾符修飾的參數(shù)在使用上的唯一區(qū)別。
Part 3 其他地方的影響
該語(yǔ)法主要是改變和避免用戶(hù)誤用、錯(cuò)用修飾符來(lái)控制對(duì)象內(nèi)數(shù)據(jù)是否只讀的問(wèn)題。
3-1 函數(shù)指針
很明顯,函數(shù)指針受到了影響。我們?cè)谛揎椀臅r(shí)候可使用 ref readonly
在元數(shù)據(jù)里,C# 使用 RequiresLocationAttribute
來(lái)限制 ref readonly
修飾符的行為。換言之,修飾符如果是 ref readonly
的話,那么該參數(shù)將會(huì)帶上 RequiresLocationAttribute
特性作為元數(shù)據(jù)底層。這樣的話可以區(qū)分已有 in
修飾符所使用的元數(shù)據(jù),避免兩者靠編譯器無(wú)法區(qū)分的問(wèn)題。