第 29 講:面向對象編程(一):面向對象和類的基本概念
C# 里最有意思的一個體系化架構就是面向對象(Object-oriented)。什么是面向對象呢?
Part 1 面向對象是什么
在 C# 的世界里,我們除了前面的簡單的例子可以通過 C# 解決之外,還可以用一種體系化的架構來搞定一個復雜的項目,這個叫做面向對象。
面向對象是什么?面向對象是將世間萬物通過代碼的形式呈現(xiàn)和體現(xiàn)出來的一種編程范式(Programming Paradigm)。所謂的編程范式,你可以理解成編碼的不同風格和方式。比如,我可以把代碼寫成數(shù)學函數(shù)調(diào)用那樣的嵌套過程,這種叫函數(shù)式編程(Function-oriented Programming,簡稱 FP);我還可以把代碼寫成 C 語言那樣,用函數(shù)作為執(zhí)行單位、并依賴于順序結構、條件結構、循環(huán)結構和跳轉結構來完成項目編碼的過程,稱為面向過程編程(Procedure-oriented Programming,簡稱 PP);最后還剩下一種編程范式,就是面向對象編程了。這里的“對象”并不是指男女朋友,而是把世間萬物的個體稱為對象。
這三種編程范式,都可以完成同樣一個任務,但是從代碼的邏輯和代碼書寫體現(xiàn)來說,寫法是不同的。而 C# 是基于面向對象編程的語言,因而我們不得不學習面向對象編程這一個編程范式。
由于面向對象這個編程范式稍微復雜一些(比較體系化),因此內(nèi)容很多。在這個教程里,我們可能會分成非常多節(jié)的內(nèi)容給大家呈現(xiàn)和介紹,希望你慢慢來。
Part 2 類
2-1 類的概念
類(Class)是貫穿面向對象的基本單位。和 C 語言的面向過程編程一樣,函數(shù)是貫穿面向過程的基本單位;而類則是面向對象的基本單位。
類是世間萬物的抽象,換句話說,如果我們要完成一個很復雜的項目,我們就得把這個項目里需要用到的所有物體用類呈現(xiàn)和體現(xiàn)出來。而類是一個整體,它用代碼寫出來,拿給別人看這段代碼的時候,它就好像在看說明書一樣:這個物體可以怎么操作、如何操作、什么時候操作。這就是類。
為什么叫類呢?你想一下,我們所有人,統(tǒng)稱叫人類。為什么叫人類呢?因為是人這個類別,因此叫人類。這個類就取自這里:我們將物體用代碼呈現(xiàn)出來的這個基本單位就稱為類,就是這個原因。
2-2 類的語法
我們使用關鍵字 class
來表示一個類。在 class
關鍵字后,緊跟一個標識符,這個名字就是這個代碼里體現(xiàn)出來的類的名稱,代表的世間萬物里究竟什么東西;后緊跟一個大括號,來表示這個類里的具體內(nèi)容(也就是我剛才說的“可以怎么操作”、“如何操作”和“什么時候操作”。
Program
。因為 C# 是基于面向對象的編程語言,因此我們不得不將 Main
方法(還有別的自己寫的方法)包裹在類里。就算是這個類沒有用,我們也得這么寫,因為基于面向對象嘛。
當然了,我們將 Main
方法寫到 Program
類,那么換句話說,這個 Main
方法是 Program
類的一個成員(Member)。
這一節(jié)的內(nèi)容我們只會接觸到方法這一種成員類型,成員還有別的類型,比如屬性、事件等等。這些類型的成員在這一節(jié)的內(nèi)容里說不到,因此我們先不管它們。
2-3 類和類的交互
另外,類是基本單位,因此我們需要書寫若干個類,來達到類和類之間的關聯(lián)。這里我們需要學習如何在類之間作調(diào)用和交互。
比如,我們擁有一個類、稱為 Algorithm
,它存儲了一個叫做 BubbleSort
的方法,專門用來對一個數(shù)組進行排序操作:
Program
請注意代碼的第 12 行(Algorithm.BubbleSort(arr);
這一行代碼)。這句話是通過 類名.方法名(參數(shù))
成員訪問符(Member Access Operator);在讀代碼的時候,我們可以翻譯成自然語言的“的”:Algorithm.BubbleSort
可讀作“Algorithm
類的 BubbleSort
方法”。
在自然世界里,如果我們要想理解這樣的代碼,我們可以認為
Algorithm
類表示和存儲算法的相關操作;拿給別人看的時候,別人就知道里面的成員都是跟算法相關的;Program
類表示程序基本的操作行為,因此拿給別人看的時候,別人就知道這個類專門給程序服務(畢竟帶了Main
方法嘛),他就不會拿著這個類干別的事情;Algorithm
類的BubbleSort
方法從名字上就可以看出是“冒泡排序”,因此對于一個老外來說,這個調(diào)用寫法就相當于算法.冒泡排序
。是不是寫成漢字更好理解呢?實際上,我們前面用到的
Console.WriteLine
、Math.Sqrt
等等方法調(diào)用下,左邊的Console
和Math
都是類的名字;而后面的WriteLine
和Sqrt
自然就是這些類內(nèi)部的成員了。
另外,
int.Parse
、string.Concat
這些方法的前面用的是關鍵字;但不知道你忘了沒有,這些關鍵字是和 BCL 名等價的。因此,這些關鍵字最終會被翻譯和解析成Int32
、String
這些內(nèi)部的名稱。而實際上,String
也是 C# 里的一個類,只是它是系統(tǒng)提供的,而不是我們自己寫的,和Console
、Math
是一樣的;而int
則稍微有點不一樣:它是一個結構。結構的內(nèi)容我們將放在“結構”一個章節(jié)里給大家介紹。不過你可以認為結構和類目前都是使用名稱.方法
的方式來調(diào)用這些方法的,因此你不必太過擔心超綱的內(nèi)容,至少目前我們還用不到結構的知識點;另一方面,因為語法是相同的(都用名稱.方法
),所以其實很好理解。
2-4 如何給類單獨創(chuàng)建一個文件
前面的內(nèi)容足以幫助我們學習和理解面向對象的編程思維,但是我們在寫代碼的時候,總不可能一個文件里放若干個不同的類吧。如果類太多,就會導致程序的代碼文件過于臃腫。一來翻的時候不好看,二來是不方便后續(xù)拓展和整體代碼,因為都寫在一個文件里,如果一個類太大了怎么辦。
因此,這一節(jié)我想給大家介紹一下,如何用 VS 創(chuàng)建一個獨立的類的文件。將一個類存儲到一個文件里,不同的類放在不同的文件里,這樣就方便我們整理和查看每一個類。
我們先打開解決方案資源管理器,然后找到項目,點擊右鍵,依次選擇“Add”(添加)、“New Item...”(新項目……)。

然后,找到“Class”(類),然后在下方文件名的地方輸入類名(就是前面的 Algorithm
),然后 .cs
Algorithm.cs
后點擊“Add”(添加)。

然后,文件創(chuàng)建好了之后,文件默認長這樣:

注意,上方的五條 using
指令目前都沒用,因此我們刪掉就行;而下面的這個類里是空白的,我們需要把前面的代碼粘貼進去;而 namespace
是用來控制命名空間的,這一點我們在最開始說過。在這里因為發(fā)揮不了作用,因此我們可以不要它。

然后,要返回 Program
類的所在文件,還是在解決方案資源管理器里找到 Program.cs
,雙擊它就可以了。
Part 3 訪問修飾符
修飾符(Modifier),指的是在類的聲明(class Program
這里),以及方法這些成員上追加的、不是返回值類型的別的關鍵字信息。比如在前面,public
、static
、private
還有最開始就知道的 unsafe
關鍵字,它們一旦寫到這些東西上,我們就可以稱為它們叫做修飾符。加在類的聲明上,我們就稱為類的修飾符;加在方法上,就稱為方法的修飾符。
修飾符分成兩類:訪問修飾符(Accessibility)和非訪問修飾符(Non-accessibility),下面我們分成這樣兩類來給大家介紹。
先來說一下訪問修飾符。所謂的訪問修飾符,本身沒有代碼層面的意義,它是用來控制成員的可訪問級別的。這么說也不明白,我們來詳細說明一下具體每一個訪問修飾符的用法,然后你自然就知道是干啥的了。
3-1 public
關鍵字
我們將 public
用于成員上,用來表達這個東西在任何時間任何地方都可以用。只要使用合適的語法(就是前面說的利用成員訪問符來寫),就可以隨便用。
可以看到,我們在 BubbleSort
方法上標記了這個關鍵字,這是為了干什么呢?這是為了告訴你,這個方法隨時都可以用。如果你沒有標記這個關鍵字的話(或者標記了別的訪問修飾符的話),這個方法就不可能隨時隨地都可以用了。所以換句話說,public
關鍵字的訪問級別最高:任何時候都可以用。
3-2 internal
關鍵字
要想理解 internal
關鍵字,我們需要介紹一下解決方案和項目之間的關系。
3-2-1 項目和解決方案的概念和邏輯關系
解決方案(Solution),指的是整個程序寫的所有代碼的整體;而項目(Project)則可以將不同的代碼分門別類地存放和歸置。解決方案是所有這個程序里的代碼的整體,那么項目可以包括同樣用途用法的代碼,因此解決方案比項目范圍更大;而項目則包含代碼文件,因此項目比文件的范圍大。
項目就是我們在解決方案資源管理器里,那個前面圖標是方框帶 C# 的這個項目??吹搅藛?,旁邊有一個三角形,點擊這個三角形,可以展開/折疊屬于整個項目里的代碼文件。

這里 TestProject
就是一個項目;而它上面的 Solution 'TestProject'
就是整個解決方案,其解決方案的名字就叫 TestProject
,這里的單引號里的內(nèi)容。
一頭霧水是吧,為啥解決方案居然和項目名是一樣的?一般來說,因為解決方案和項目的關系是“解決方案 > 項目”。但實際上,可以劃等號:大多數(shù)時候,我們在教學和初學 C# 編程的時候,一個解決方案只需要容納一個項目就可以了。既然一個解決方案只包含一個項目,那么整個項目名稱就完全可以和解決方案的名字是一樣的,單純是為了簡單。如果取名不一致的話,可能你會覺得解決方案的代碼亂糟糟的。
3-2-2 internal
的實際意義
internal
關鍵字一般用在類的聲明之上(當然也可以用在成員上),這表示這個類或者這個成員,是整個項目里的任意位置可以用;但超出這個項目(比如這個解決方案里有倆項目,另外一個項目里調(diào)用這個成員或者類)的話,這個時候,你就“看不見”它了:或者換句話說,你就不可以用這個東西了。

因為創(chuàng)建新項目這個內(nèi)容我們沒有必要用,所以這里就不展開給大家講解怎么在同一個解決方案里創(chuàng)建新的項目了。
創(chuàng)建項目和創(chuàng)建類代碼文件的方式是一樣的,只是選擇的東西是 Add project 而不是 Add new item;而且在點選創(chuàng)建的時候,不是在項目上(因為項目里只能創(chuàng)建文件,項目不能嵌套項目),而是只能在解決方案上點右鍵選擇創(chuàng)建。

很遺憾,可以看到代碼上報錯了。它提示的文字是“'ANewClassInAnotherProject
' is inaccessible due to its protection level”。翻譯過來就是,這個類因為訪問級別低的關系,你無法使用它。
這里演示給大家看,就是這么一個道理:如果用了 internal
3-3 private
關鍵字
最后一個關鍵字是 private
。我們可以看到,目前我們就只有 Main
方法上用到了這個訪問修飾符。這個訪問修飾符指的是,這個成員僅在這個類里面隨便用;超出去的任何位置(不管是別的類還是別的項目),都不可以使用此成員。
可以看到,因為 Main
方法的特殊性,這個方法僅提供給系統(tǒng)自動調(diào)用,因而設置為 private
是最安全也是最正確的行為:因為我們禁止讓外部的任何一處使用和調(diào)用 Main
這個特殊的方法。
當然了,Main
方法本身是特殊的方法,因此你也別想著遞歸調(diào)用它,或者是在類的別處調(diào)用 Main
方法本身。雖然這個寫法編譯器并不會管你:
確實是允許的,但這樣很危險:因為 Main
本身就是系統(tǒng)特殊方法,你自己用指不定會出什么代碼的 bug 呢。如果你遞歸學習得并不是特別理想,這樣的程序必然會導致嚴重問題的出現(xiàn)。不過從語法上講,因為設置的 允許在類里的別處隨便使用它,因此語法是允許這么做的。
C# 的標準版一共有 5 種訪問修飾符,但目前只能講清楚這三種,剩下的兩種我們需要在講了“繼承”特性后,才能說??傊阆劝堰@三個記住就可以了。其中,最低的是 private
,只能在類里隨便用。當然,也沒有比 private
還低的級別了,因為級別再低的話,就沒有意義了;最高的是 public
,隨時隨地都可以用。
3-4 其它的一些問題
當然了,前文的代碼還有一些小的細節(jié)需要說明清楚,因此我們這里列出來給大家介紹一下。
3-4-1 類也是可以修飾訪問修飾符的
顯然,前面的類標記了 internal
就是一個典型的例子。但是,我們來思考一下,給類的聲明上標記 private
是不是可行的寫法?顯然,private class
的組合是不科學的,因為類是面向對象的基本單位,因此類和類之間是獨立的代碼塊。我們要使用這個類,至少也需要保證類本身是可以在項目里訪問。否則,這個類就失去了意義:比如說,我給類標記了 private
,那么就說明類本身只能在這個類里可以用。這是個啥?整個解決方案代碼那么多,居然存在有一個類完全獨立開別的代碼,只能夠自己使用自己,是不是說不過去?
因此,類的訪問修飾符最低也必須是 internal
,而且,類僅只能用 internal
和 public
這兩種訪問修飾符。就算我們后面把剩下的兩個訪問修飾符都說了,類依舊只能用這倆訪問修飾符對訪問級別進行修飾。
3-4-2 類的訪問修飾符和成員的訪問修飾符不一致,怎么理解?
我們舉個例子:
internal
,但 BubbleSort
這個成員卻用的是比 internal
大的訪問級別 public
。那么這個 BubbleSort
到底能不能訪問呢?最終的訪問級別是怎么樣的呢?
實際上,套在內(nèi)部的 BubbleSort
會受到外部 internal
級別的影響,保證這個方法的級別是取 internal
和 public
里較小的那個級別。換而言之,既然類都是 internal
的,那么里面設置的級別肯定得基于 internal
來作判斷,對吧。總不能里面成員的訪問基本比類的訪問級別還大,那就說不過去了。
代碼書寫上,內(nèi)部寫的是 internal
還是 public
都無所謂,因為最終取的還是 internal
;但是實際上,寫 public
就是為了省事。因為我們這么設置訪問級別的話,假設有一天我想把 Algorithm
類暴露(Expose)出來給用戶用了的話,就不一定非得是項目內(nèi)的使用了;如果你內(nèi)部的成員設置的是 internal
的話,根據(jù)級別要取較小的原則,別的用戶還是用不了成員。因此,這么寫組合是為了以后代碼的可拓展性,是一個好的習慣。
總之:要注意兩點:
成員的最終可訪問級別,是取的類的訪問級別和里面的成員的訪問級別的較小者;
按照習慣,如果類需要修飾
internal
的話,那么成員依舊使用public
,除非這個成員本來就不是給外人用的。
3-4-3 訪問修飾符可以不寫嗎?
C# 里為了保護代碼的安全性,一般是取最小的原則:盡量越小越好。因此,C# 是做了這么一個代碼約定的:如果不寫的話,默認就是這個成員可訪問級別的最小的這種。舉個例子,類的最小訪問級別是 internal
,因此如果不寫訪問修飾符到類的聲明上的話,我們就默認這個類是 internal
的;如果成員沒有書寫訪問修飾符的話,那么我們默認這個方法是只能在類里隨便用的,即 private
的。
所以,讓我們來總結一下這幾個訪問修飾符:
public
關鍵字:隨時隨地都可以用。internal
關鍵字:只有當前的項目里可以用;而出了這個項目后,這個東西就不能用了;private
關鍵字:只能用在類里面的成員,表示只能在這個類里隨便用;超出去的任何位置都是不能用的。
另外,訪問修飾符的默認情況如下:
類的聲明上,如果缺省訪問修飾符,默認是
internal
;成員上,如果缺省訪問修飾符,默認是
private
。
Part 4 static
修飾符
因為非訪問修飾符,我們就用到了
static
,而unsafe
這類修飾符我們在之前已經(jīng)介紹過,因此這一部分的內(nèi)容我沒有寫成“非訪問修飾符”,而是寫的“靜態(tài)修飾符”。
4-1 實例和靜態(tài)的概念
static
一詞對于初學者來說非常不友好,因為它并不是很好從單詞的字面意思上理解。方法是類的執(zhí)行核心,我們可通過 類.方法
的格式使用它,那么自然而然就意味著方法我們可以無憂無慮地使用它們,只要訪問修飾符級別合適,方法的調(diào)用肯定是得心應手的。
不過,C# 還存在一類成員,稱為實例成員(Instance Member)。和靜態(tài)成員不同,實例成員往往會和一個物體或個體做一個綁定。比如減肥、跑步等行為,如果我們要寫成代碼的話,就必然會和一個人的個體單獨進行綁定(因為跑步和減肥都是一個人自己的行為)。如果人的減肥和跑步這些行為我們依然使用 static
來表達的話,顯然就不合理了??梢詮拇a里看到,我們前面使用和利用的行為,都不需要依賴于個體就可以執(zhí)行:比如 int.TryParse
、Console.WriteLine
、Math.Sqrt
。第一個是把字符串解析成整數(shù)的行為,它顯然并不依賴于任何一個整數(shù)個體;而控制臺打印文字到屏幕上的過程,也不是依賴于哪一個控制臺,因為我們就一個控制臺用來顯示內(nèi)容,因此它并不依賴于一個泛指的個體。你可能會問我,求平方根總是依賴于一個數(shù)值了吧!是的,但是 C# 代碼的思維是:求平方根是一個統(tǒng)一規(guī)范的操作流程,它是綁定一個數(shù)值“參與運算”,而并不是“依賴于”數(shù)值本身。另外,Math
這個類還包含了很多方法,可提供給我們使用(比如求絕對值啊、正弦啊、取對數(shù)什么的)。計算我們可以讓它按類型進行綁定來使用,就需要把這些東西寫進這個類型的類(或是之前說的結構)的代碼文件里。但是,這樣又會使得整個類型的代碼變得相當多。因此,利用靜態(tài)成員而不是實例成員的思維方式,可以避免類型變得很臃腫。
關于實例的用法和語法,我們將在下一節(jié)的內(nèi)容給大家介紹。它的內(nèi)容也是非常多,要慢慢來學才行。
4-2 讓我們現(xiàn)在再來理解靜態(tài)方法
說完靜態(tài)和實例的區(qū)別后,我們再回到頭看 static
方法,你就會輕松不少。我們之前寫了不少的代碼,比如求質(zhì)數(shù)之類的程序。我們再次把代碼放到這里。
請觀察代碼的修飾符。我們最開始就強制性讓大家添加 static
關鍵字。這是有道理的:首先,Main
方法并不依賴于什么東西,它是系統(tǒng)自動調(diào)用的方法,是一個特殊的方法,因此我們必須在 Main
上追加 static
關鍵字;而其它的方法,都是在 Main
里得到了調(diào)用。既然 Main
都是靜態(tài)的了,那么我們沒有理由給這些其它被調(diào)用方法讓他們改實例的:因為它們是 Main
調(diào)用的,Main
Program
的實體對象,才能計算這些東西吧。顯然沒有必要也沒有意義(畢竟,一個 Program
的實體是什么,這我怎么理解都不知道)。因此,定義成靜態(tài)的方法,顯然是正確的思維。
靜態(tài)我們就先說到這里。
順帶一提,這些方法都是給
Main
方法服務的,所以肯定不能給別處用了,自然就沒有寫訪問修飾符。當然了,寫也最好寫private
,你說是吧。
Part 5 文檔注釋
為了幫助我們書寫代碼,和查看代碼的相關信息,C# 提供了一種機制,叫做文檔注釋(Documentation Comment)。文檔注釋可以直接將代碼的描述信息呈現(xiàn)到代碼貼士里,以便我們鼠標放到每個成員上查看信息的時候,可以直接看到描述文字。
當然,如下我們會介紹非常多的文檔注釋的相關寫法,但是有些我們用不上,就簡單說一下;有些重要的我們就寫詳細一點。
5-1 文檔注釋的架構
我們先來說一下文檔注釋的用途和架構。文檔注釋用三斜杠開頭:///
,在斜杠后,書寫注釋文字。它們并不會影響程序的執(zhí)行,因為它們是注釋文字。但是,文檔注釋提供了一個規(guī)范的書寫格式,只要我們按照格式寫,你就可以發(fā)現(xiàn),這些注釋文字就會顯現(xiàn)在代碼貼士上。

圖上這個小條我們暫且稱它“代碼貼士”。一旦我們在上方追加敘述文字后:

你就會發(fā)現(xiàn),這段文字的描述信息就呈現(xiàn)上來了。只要我們鼠標放在 InputValue
上,我們就可以看到它。
文檔注釋是通過類似 XML 的擴展語法標記來完成說明的,它可以寫在類上,也可以寫在成員上。文檔注釋用到的標記非常多,但是都有自己的用途,因此有必要給大家說明清楚具體用法。
下面我們來說一下基本的零部件。
5-2 基本文檔注釋塊
5-2-1 summary
塊
圖上就用到了 summary
。我們寫上成對的 summary
,前面用尖括號,后面也是尖括號,但里面的 summary
左側要加上一個斜杠,用來表示標記是結束的。在期間,我們書寫文字,這些文字是用來描述被注釋的對象(方法啊、類什么的)的基本信息用的。文字可以有很多,如果一行寫不下的話,可以換行書寫。
5-2-2 remarks
塊
和 remarks
塊差不多,它也是描述文字,用來表示這個東西的基本信息的。不過區(qū)別在于,remarks
塊是可選的,你可以不寫 remarks
,但 summary
是必須有的。remarks
是補充說明文字,比如說我們要對求質(zhì)數(shù)的核心方法寫文檔注釋的話,summary
塊的內(nèi)容可能是“計算一個數(shù),是否是一個質(zhì)數(shù)?!?;但 remarks
的文字則可能是“請注意,由于是計算質(zhì)數(shù),因此傳入的數(shù)字必須得是一個大于 1 的正整數(shù)”。
5-2-3 returns
塊
如果方法具有返回值的話,我們可能需要用 returns
塊來表達這個方法的返回值到底返回了個什么。比如說,判別質(zhì)數(shù)的返回值一定是一個 bool
結果,那么 returns
塊里的內(nèi)容可能就是“一個 bool
類型的數(shù)值,用來表示是否是質(zhì)數(shù):是則返回 true
,否則返回 false
”。
5-2-4 param
和 paramref
塊
param
塊是一個單標記的 XML 塊,它專門描述敘述一個參數(shù),以及參數(shù)的對應解釋。舉個例子,在 IsPrime
方法里,我們需要傳入一個 int
類型的數(shù)據(jù)。這個時候,我們可以在方法的文檔注釋里追加一行 <param name="number">The number to check.</param>
,就可以達到描述參數(shù)的效果。
當然,因為是描述參數(shù)的文字,所以它不會直接呈現(xiàn)到代碼貼士里。你需要在調(diào)用的時候才看得見:

調(diào)用方:

可以看到,只有在輸入了這個左小括號 (
后,才會提示這個文字信息。
當然了,我們只需要追加到文檔注釋里,因此我們不需要在意書寫
param
標記的具體位置。你可以把它放在最開始,也可以放在最后面。只要包含這個解釋文字,那么文字就會正常顯示到代碼貼士里。
接著,我們可以在文檔注釋的別處引用這個參數(shù)。舉個例子,我們在描述 number
參數(shù)的時候,可以類似前面講解 remarks
塊那樣,追加解釋文字,提示用戶在使用的時候建議使用大于 1 的正整數(shù)。不過,這個時候我們會用到 number
參數(shù)作為解釋的一部分:“請注意參數(shù) number
需要大于等于 2”。此時這個 number
你可以手寫到文檔注釋里,不過建議的格式是使用 paramref
這個單標記的塊。
5-2-5 example
塊
example
塊很少用到,它也不會在任何時候呈現(xiàn)到代碼貼士里。example
是追加到文檔注釋里,表示代碼使用的一些相關示例的。你可以在 example
塊里追加一些格式。
5-2-6 exception
塊
很多時候,可能有方法需要自己產(chǎn)生異常(通過 throw
-new
語句拋出一些異常)。這個時候,我們可以通過文檔注釋,提供給用戶,讓用戶在查看方法本身的時候,可以看到這個方法可能會在內(nèi)部產(chǎn)生的異常。
它的格式和 param
差不多,它的格式也是差不多的。不過這里,參數(shù)用到的是 name=""
的格式,而這里,我們寫的是 cref=""
,這一點需要你注意。
FormatException
異常類型。不過在用戶查看的時候,注釋文字是看不見的,而是顯示成類似這樣:

5-3 內(nèi)聯(lián)文檔注釋塊
有一部分寫法,是內(nèi)聯(lián)到前面介紹的塊里的,并不是單獨寫的。下面介紹一些基本的寫法。
5-3-1 c
、code
塊
c
和 code
塊都是嵌入到 summary
啊、remarks
里使用的內(nèi)聯(lián)塊。它們表示一段代碼或者內(nèi)聯(lián)的代碼。比如我們現(xiàn)在在書寫這篇文章的時候,“summary
和普通單詞 summary”的區(qū)別在于,前者我們使用了內(nèi)聯(lián)代碼的渲染。我們也可以把這樣的東西寫進文檔注釋里。寫法是 <c>代碼</c>
。比如說,前文描述 example
塊的時候,我們就會使用代碼來描述文字,幫助用戶會使用這些東西。
bool result = IsPrime(17);
順帶一提,
<see langword="for"/>
用的時候不多,但是很有趣。和前面寫<see cref=""/>
格式的文字不同,這個langword=""
里寫的往往都是一些關鍵字,這表示在渲染和顯示文檔注釋的時候,把這個文字按關鍵字的形式呈現(xiàn)出來。比如說,我們用的 VS 里把關鍵字顯示成藍色,那么文檔注釋里,如果寫了<see langword=""/>
的話,那么這個里面的單詞就會呈現(xiàn)成藍色。
5-3-2 u
、i
、b
和 a
塊
這個相比不用多說了。這個是 HTML 沿用下來的標記。u
是下劃線,i
是斜體,b
是加粗,而 a
則是超鏈接。
我們拿 a
標記來舉例說明用法。
比如這里第 6 行就是一段引用 a
標記的寫法。而查看代碼貼士的時候:

你就可以發(fā)現(xiàn),這個鏈接可以點。
5-3-3 para
塊
para
塊就是作為一個段落呈現(xiàn)的。比如前文里,我們可以把它拆成兩個段落來呈現(xiàn):
這樣嵌套兩段文字就可以了。
5-3-4 list
塊
最后最麻煩的是這個 list
塊。list
塊有三種用法,分別是“渲染表格”、“渲染有序列表”和“渲染無序列表”。我們挨個來說一下。
IsPrime
函數(shù)里,我們介紹了返回值。但是返回值的描述并不詳細,因此我們可以列表來表達。上方的這個描述文字寫起來很丑,不過可以將就著看。
我們使用 <list type="table">
和 </list>
一對標記來表達一個表格。里面是表格的具體內(nèi)容。接著,我們使用 item
標記來寫一行文字。表格只能由兩列構成,這個是文檔注釋限制了的,因為這里的表格僅用來表達“數(shù)值:意思”這樣的一組概念。
在 item
塊里,我們嵌套 term
和 description
塊分別表示“數(shù)值:意思”的“數(shù)值”部分和“意思”部分。到時候,代碼貼士將我們這個表格渲染成這樣:

前面我們說的是 list type="table"
,接著我們來說下別的值。list type="number"
表示渲染一個有序列表;而 list type="bullet"
則渲染一個無序列表。它們里面呈現(xiàn)項目的用法是一致的,所以我們一起說。
在呈現(xiàn)有序或無序列表的時候,我們里面就只需要嵌套一個單純的 item
塊就可以了。我們不需要在 item
里繼續(xù)嵌套 term
和 description
塊,因為這倆是給表格提供渲染用的。
如果要寫若干項目,我們只需要挨個寫出來內(nèi)容(文字),直接丟進 item
里就可以了。
這樣就可以了。
5-3-5 see
和 seealso
塊
see
塊和 seealso
塊用來引用除了參數(shù)之外的別的東西,比如說類啊、方法之類的。舉個例子。
可以從這個方法的文檔注釋里看到,我們內(nèi)嵌了一個 <see cref="string"/>
和 <see cref="IsPrime(int)"/>
,這恰好表示我這里引用了類型 string
和方法 IsPrime
,并且 IsPrime
帶了一個參數(shù)是 int
類型。
這么寫的作用是為了表達實際上真正的類和方法在哪里。如果你直接寫 string
和 IsPrime
的文本到注釋里的話,我們就無法通過純文本來反推出這個玩意兒的具體位置。而寫成 see
的話,當鼠標移動到寫文檔注釋的這個成員上、系統(tǒng)會彈出代碼貼士的時候,我們可以直接用鼠標單擊這個信息,就可以跳轉到對應的代碼位置上去。

IsPrime
后,代碼就會自動跳轉到 IsPrime
方法那里去。
另外,seealso
和 see
差不多,只是 seealso
是單獨用的。它寫在最外面,表示在文檔注釋里用到的(用 see
塊引用了的)類型、成員信息。就好像你寫的論文里的“參考文獻”、文章里的“另請參考”這種東西。
5-4 文檔注釋的注釋
呃,我們甚至可以給文檔注釋本身寫一個注釋文字信息,來提供一些幫助程序員自己開發(fā)代碼的時候完善文檔注釋的幫助文字。它的格式和 HTML 的一樣,也是 <!-- 文字 -->
。
?/// <!-- This paragraph is incomplete, waiting for developers finishing this. -->
文檔注釋的注釋是不呈現(xiàn)也不會渲染的,因此我們大大方方寫進去就可以了。
5-5 完整例子
總之,這里給大家提供文檔注釋的書寫規(guī)范:我們使用前面求質(zhì)數(shù)的程序給大家展示文檔注釋。