C# 方法的簽名上最多可以由多少個關鍵字構成?
這是一個非常有趣的問題??赡芪覀冊谄綍r使用的過程之中并不會這么去用,但是這對我們了解一些新鮮、罕見的語法有一定的蛇皮幫助。
題目介紹
首先,我們知道,一個方法需要有簽名(Signature)一說。它指的是我們在書寫方法的時候,那個方法的頭部。比如下面的這個代碼:
public
和 static
。這很好理解。一個方法無需修飾符的時候,它默認會和 C 語言的理解進行一定程度上的兼容和匹配。只不過,static
在 C# 里已經具有一個比較大的變化。
那么,請問一個方法最多可以有多少個修飾符構成呢?這一次我們把問題“廣義化”到,返回值類型如果是關鍵字也算修飾符的一部分。也就是說,這個問題如果改成這樣的話,那么剛才的這個 Main
方法我們將算成三個修飾符。而題問的是,一個方法最多可以由多少個修飾符構成。
很明顯,這個問題可能在不同的 C# 語言版本里有不一樣的答案。我們目前將 C# 12 納入其中進行計算,那么它應該是多少呢?
開始構造
首先我們需要非常熟悉 C# 的修飾符的用法。C# 的修飾符非常多,我們主要在面向對象里會用到如下的修飾符種類:
訪問修飾符(
public
、protected
這些)多態(tài)修飾符(
new
、virtual
、abstract
和sealed
)靜態(tài)修飾符(
static
這一個)一些普通的類型關鍵字(
int
、string
等)
但是,光靠這些是不夠長的。我們舉個例子。
我們知道,訪問修飾符里有一個不起眼的修飾級別:protected internal
。這個級別是組合關鍵字用法,表示的是該成員在當前程序集里可以隨便看到;但是出了程序集之后,你只能在從這個成員的對應類型進行派生了之后才可以看到。那么,它因為用到兩個關鍵字,所以我們納入進來。
其次是 static
。我們一般習慣把靜態(tài)的修飾符放在訪問修飾符旁邊,這樣一眼就看得到。
然后是多態(tài)修飾符。這次我們知道的是,面向對象不允許我們使用 abstract
在靜態(tài)成員上,所以多態(tài)修飾符非得加進來的話,只能用 new
。
最后算上返回值類型,隨便挑一個吧。
這樣的話,把 int
算進去就有 5 個了。但這肯定不是極限。
一些不太常用的修飾符
我們再來想一想。這個問題針對的是方法,而方法在 C# 里有一個比較不起眼的特性教分部方法(Partial Method)。這個特性允許我們將方法拆解為聲明部分和代碼實現(xiàn)部分,存儲在兩個不同的文件之中。這樣做的目的其實是為了便于編譯器生成代碼的時候,在不改動源代碼的時候可為類型進行擴展;也可以往分部方法上標記特性,這樣可以達到一些比較方便的生成行為。
但,這個方法是非 void
返回的,而且訪問修飾符級別也不是 private
,這意味著我們不能使用分部方法的特性。其實不然。從 C# 9 開始,分部方法允許我們使用它來作用于任何方法。所以,代碼可以這樣:
很好。這樣夠了嗎?并不夠。C# 9 允許的分部方法雖然推廣到任意方法之上,但很遺憾的是,因為方法本身帶有返回值類型,訪問修飾級別也并非原有的 private
,所以可能在使用代碼的時候造成副作用。而分部方法有一個非常神奇的規(guī)則是,它可以不實現(xiàn)。如果你將分部方法的聲明部分寫出,哪怕你不去實現(xiàn)它,你在代碼里也可以使用該方法。雖然它沒有實現(xiàn),它等于是在運行時沒有效果,但它并不影響編譯。如果你實現(xiàn)了,你就可以獨立在單獨的文件里實現(xiàn),讓方法跑起來。這樣甚至不用改動原來的文件。
但是,這特性在 C# 9 里,因為訪問修飾符、返回值類型等因素,該方法就無法保證實現(xiàn)的安全性。因此,它必須給出實現(xiàn)部分。因此,我們只能將其拆解為兩個文件。為了保證該方法簽名的穩(wěn)定性。
這樣似乎已經到了 6 個關鍵字的地步了。其實并不夠。我們還有一個殺手锏:extern
關鍵字。
該關鍵字可謂在 C 語言里用得特別多。在 C# 里基本就遇不到了??赡苣銓?P/Invoke(互操作性)的時候會用到它。它表示該成員的實現(xiàn)部分不是 C# 來做的。你往成員的修飾符里加了它之后,成員即使直接以分號結尾也不會影響程序的編譯,因為編譯器認為你的代碼走的是別的語言實現(xiàn)的、或者是 C# 實現(xiàn)的代碼,但放在了獨立的 dll 文件之中。
因此,加上該修飾符,我們可以達到 7 個修飾符的地步,且不需要給出實現(xiàn)。
unsafe
關鍵字。
該關鍵字用來表示該代碼段落是不安全的。換言之,該段落的代碼用到了 C# 里的指針操作。比如 ->
運算符、void*
以及 C# 9 里才有的函數(shù)指針。假設我們將返回值替換為函數(shù)指針類型,那么方法聲明就有些“耍賴”了——因為函數(shù)指針的聲明是允許遞歸的。比如說,delegate* managed<void>
表示返回 void
的、由 C# 程序集里給定的靜態(tài)方法,指向這樣的方法的指針;而返回值替換為函數(shù)指針:delegate* managed<delegate* managed<void>>
也算允許存在的。
C# 7 開始允許我們將返回值聲明為引用返回,這樣的方法將返回一個對象的引用。ref T
表示返回一個 T
類型實例的引用;而 ref readonly T
則表示一個 T
類型的只讀引用,即從上面?zhèn)飨聛恚銦o法改動它指向的對象的值。比如對對象進行自增操作,它改變了內部的數(shù)值;但你可以用到它的引用來做一些事情。
那么這樣的話,就好說多了。我們可以使用 ref readonly int
很好。這樣我們就達到了 10 個修飾符的地步。這便是最多的情況。
那么,這是答案嗎?No??墒俏覀円呀洘o法再繼續(xù)進行推廣了啊。static
修飾符意味著方法不能追加 readonly
修飾符來表示方法不改變 this
的內部數(shù)值(如果 class C
改成 struct C
的話);而 async
修飾符又不允許我們使用引用,而該方法返回了一個只讀引用。這看起來并不能繼續(xù)了;而 override
則可以用于 class
類型,但這個方法是靜態(tài)的,你也無法繼續(xù)進行重寫之類的操作。
那我們構造一個?
B
類型里最后的這個實現(xiàn)部分用到了最多的情況,而且用的是實例的實現(xiàn)模式,沒有用 static
關鍵字。這樣的目的是為了可以用 override
關鍵字;而這樣也可以使用 sealed override
或者是 abstract override
組合修飾來達成派生的方法重寫模式。
另外,該例子的其他修飾符也都用上了,但很遺憾的是,這例子也是 10 個關鍵字。
那,真的沒有辦法繼續(xù)了嗎?答案是否定的。
接近答案
讓我們發(fā)散地想一想。既然靜態(tài)成員無法抽象,那有沒有可能,我們讓靜態(tài)成員抽象起來呢?C# 11 的接口靜態(tài)抽象成員
其他問題
1、override
不能用構造放進去嗎?
很遺憾,做不到。因為接口不能用 override
修飾符。而如果你基于普通類型的話,又無法使用靜態(tài)抽象成員了,因此沒可能同時兼具。
2、還能更多嗎?
不能。這是本題的最終解決方案。不過 C# 可能會在以后允許在接口里聲明 readonly
修飾的成員來保證對象的 this
指針的內部數(shù)值只讀。于是,它還能加一個修飾符,成為最多的情況。
與此同時,比較新的 scoped
關鍵字目前只能修飾在參數(shù)和臨時變量上,我們無法將其放在別的地方,所以 scoped
關鍵字在這個題目里用不著。
另外,如果我們將運算符也算方法的話,我們可能會考慮出更多的組合。比如說運算符就可以多一個 checked
修飾符(operator checked
的聲明),但它不能改變訪問級別,它只能是 public