探索 C# 10 的泛型特性
特性在 C# 里一直是地位很高的語(yǔ)法機(jī)制。它提供了一種高配版的注釋信息,寫入到元數(shù)據(jù)里,并且一直長(zhǎng)期存在。和注釋不同,特性可使用反射直接獲取內(nèi)容,而注釋不參與編譯因此無(wú)法從代碼機(jī)制來獲取。
C# 10 推廣了特性,使得特性可以用泛型。
基本語(yǔ)法
我們使用同樣的泛型語(yǔ)法修飾和使用到特性類型上去:
就可以了。
在使用的時(shí)候:
即直接使用尖括號(hào)即可。和之前的用法一樣,無(wú)參可以省略小括號(hào),特性后綴 Attribute 可以省略。
支持的泛型類型
既然都說到了泛型特性,那么我們就得說一下規(guī)范用法,以及 C# 允許的泛型特性用法。首先是泛型的實(shí)體類型的支持。
首先給出結(jié)論。

這個(gè)表看起來有點(diǎn)大,是因?yàn)橛泻芏嗉?xì)節(jié)要說的。我們把所有不可以作為泛型參數(shù)的類型都說一下,具體是為什么。
指針類型:之所以指針類型不可以,是因?yàn)榉盒皖愋蛥?shù)本身的限制。泛型類型參數(shù)在提供給代碼和 IDE 服務(wù)的時(shí)候,在無(wú)泛型類型參數(shù)的約束的時(shí)候,默認(rèn)是走
object
里作為數(shù)據(jù)信息顯示的。這就是為什么你在對(duì)一個(gè)沒有任何約束的泛型類型參數(shù)為類型的對(duì)象使用.ToString
會(huì)直接給你顯示object.ToString
的原因;而眾所周知,指針是不走任何數(shù)據(jù)類型派生的特殊數(shù)據(jù)類型,因此指針類型在 C# 是不能作為泛型類型參數(shù)的。動(dòng)態(tài)類型:
dynamic
類型是一種特殊的類型,它可以在你任何時(shí)候使用的時(shí)候都不產(chǎn)生報(bào)錯(cuò),以此方式來簡(jiǎn)化反射調(diào)用。可問題就在于它自身的類型是不確定的,dynamic
這個(gè)類型只是美其名曰給了一個(gè)標(biāo)記,以便書寫代碼,實(shí)際上在使用的時(shí)候,它什么類型都可以充當(dāng)。因此,特性里記錄元數(shù)據(jù)信息的時(shí)候不能記錄一個(gè)可變數(shù)據(jù),因此動(dòng)態(tài)類型不被允許。引用結(jié)構(gòu):
ref struct
修飾的引用結(jié)構(gòu)類型在 C# 7 里誕生出來就不被允許作為泛型類型參數(shù),因?yàn)樗鼉H放在棧內(nèi)存里。而普通的結(jié)構(gòu)是可以通過裝箱進(jìn)入堆內(nèi)存的,因此為了優(yōu)化內(nèi)存使用和優(yōu)化性能,這種數(shù)據(jù)類型是不可以任何形式進(jìn)入堆內(nèi)存的。而剛才說過,泛型類型參數(shù)在沒有任何約束的時(shí)候是走object
類型的,而object
是引用類型,因此不被允許。可空引用類型:C# 8 的可空引用類型允許用戶對(duì)引用類型使用可空記號(hào)
?
來表明它的可空性。但是,在底層代碼分析上,這個(gè)記號(hào)是不存在的,它們被轉(zhuǎn)換為了特性標(biāo)記存儲(chǔ)到了元數(shù)據(jù)里。換言之,如果我們?cè)试S可空引用類型作為泛型類型參數(shù)的話,此時(shí)記錄元數(shù)據(jù)的時(shí)候就必須有辦法去區(qū)分類型T?
和T
。而實(shí)際上這是做不到的。所以,可空引用類型是不允許的。平臺(tái)敏感整數(shù)類型:C# 9 有兩個(gè)類型
nint
和nuint
用于向下兼容 C/C++ 里的int
和unsigned
類型,用于簡(jiǎn)化和更加便利去使用平臺(tái)調(diào)用(P/Invoke)代碼,更好體現(xiàn)可交互性。問題在于,nint
和nuint
并不是真實(shí)存在的數(shù)據(jù)類型,在底層也都被翻譯和轉(zhuǎn)換為了IntPtr
和UIntPtr
類型了,因此實(shí)際上它們并不是真實(shí)存在的數(shù)據(jù)類型,所以是不被允許作為泛型類型參數(shù)的,因?yàn)樽陨碓谶\(yùn)行時(shí)就不存在,就談不上什么元數(shù)據(jù)存儲(chǔ)了。開放的泛型類型:這種開放的泛型類型,在 C# 里目前只有唯一一個(gè)語(yǔ)法可以使用:
typeof
表達(dá)式。你可以使用typeof(List<>)
來獲取一個(gè)帶一個(gè)泛型類型參數(shù)的List<T>
類型的類型本身信息。但是,你無(wú)法使用List<> l = new List<>();
之類的語(yǔ)法來創(chuàng)建沒有給出明確類型的開放泛型類型。所以,C# 機(jī)制本身也就限制了無(wú)法把這個(gè)玩意兒作為泛型類型參數(shù)里:泛型類型參數(shù)目前沒有閉合,因此是可變的。泛型參數(shù):泛型參數(shù)是可變的,和前文的開放泛型類型是一樣的道理,因此不允許。
可空泛型類型(有值類型或引用類型約束):同上。約束只是限制它在哪個(gè)范圍里取值,但沒有限制泛型類型本身的唯一性。
可空泛型類型(沒有任何約束):同上。
可把泛型類型用于泛型特性的任何一個(gè)地方
比如,你可以這么寫代碼:
P
特性接收的是 T
類型的參數(shù),并且賦值給了 T
類型的 Element