文件註解

C# 來源檔案可以有結構化的註解,這些註解會產生這些檔案中所定義型別的 API 文件。 C# 編譯器會產生 XML 檔案,其中包含代表註解和 API 簽章的結構化資料。 其他工具可以處理該 XML 輸出,以網頁或 PDF 檔案等的形式建立人類可閱讀的文件。

此處理序提供許多優點,可讓您在程式碼中新增 API 文件:

  • C# 編譯器會將 C# 程式碼的結構與註解的文字結合成單一 XML 文件。
  • C# 編譯器會驗證註解是否符合相關標籤的 API 簽章。
  • 處理 XML 文件檔的工具可以定義這些工具專屬的 XML 元素和屬性。

Visual Studio 之類的工具會為文件註解中使用的許多常見 XML 元素提供 IntelliSense。

本文涵蓋下列主題:

  • 文件註解和 XML 檔案產生
  • C# 編譯器和 Visual Studio 驗證的標籤
  • 產生的 XML 檔案格式

建立 XML 文件輸出

您可以撰寫以三斜線表示的特殊註解欄位,為程式碼建立文件。 註解欄位包含的 XML 元素描述註解後面的程式碼區塊。 例如:

/// <summary>
/// This class performs an important function.
/// </summary>
public class MyClass { }

您可以設定 GenerateDocumentationFileDocumentationFile 選項,編譯器會在原始程式碼中找到所有具有 XML 標籤的註解欄位,並從這些註解建立 XML 文件檔。 啟用此選項時,編譯器會針對專案中宣告的任何公開可見成員產生 CS1591 警告,而不需要 XML 文件註解。

XML 註解格式

使用 XML 文件註解時需要分隔符號,以指出文件註解的開始和結束位置。 您可以搭配使用下列分隔符號與 XML 文件標籤︰

  • /// 單行分隔符號:文件範例和 C# 專案範本會使用此表單。 如果分隔符號後面有空白字元,則不會包含在 XML 輸出中。

    注意

    當您在程式碼編輯器中輸入 /// 分隔符號之後,Visual Studio 會自動插入 <summary></summary> 標籤,並將游標放在這些標籤內。 您可以在 [選項] 對話方塊中開啟或關閉這項功能。

  • /** */ 多行分隔符號:/** */ 分隔符號具有下列格式化規則:
    • 在包含 /** 分隔符號的行上,如果該行的其餘部分是空白字元,則不會處理該行的註解。 如果 /** 分隔符號後面的第一個字元是空白字元,則會忽略該空白字元,並處理該行的其餘部分。 否則,會將 /** 分隔符號後面的整行文字處理為註解的一部分。

    • 在包含 */ 分隔符號的行上,如果到 */ 分隔符號為止都只是空白字元,則會忽略該行。 否則,會將到 */ 分隔符號為止的文字處理為註解的一部分。

    • 針對在開頭為 /** 分隔符號之行後面的行,編譯器會尋找每行開頭的常見模式。 模式可以包含選擇性空白字元和/或星號 (*),後面接著更多選擇性空白字元。 如果編譯器在開頭不是 /** 分隔符號或結尾是 */ 分隔符號的行開頭發現常見模式,則會忽略每行的該模式。

    • 下列註解中唯一會處理的部分是開頭為 <summary> 的那一行。 三個標記格式會產生相同的註解。

      /** <summary>text</summary> */
      
      /**
      <summary>text</summary>
      */
      
      /**
      * <summary>text</summary>
      */
      
    • 編譯器會識別第二行和第三行開頭的一般模式 "*"。 模式不會包括在輸出中。

      /**
      * <summary>
      * text </summary>*/
      
    • 因為第三行的第二個字元不是星號,所以編譯器在下列註解中找不到常見模式。 會將第二行和第三行的所有文字都處理為註解的一部分。

      /**
      * <summary>
         text </summary>
      */
      
    • 基於兩個原因,編譯器在下列註解中找不到任何模式。 首先,星號前面的空格數目不一致。 其次,第五行的開頭是 Tab,這與空白字元不符。 會將第二行到第五行的所有文字都處理為註解的一部分。

      /**
        * <summary>
        * text
      *  text2
       	*  </summary>
      */
      

若要參考 XML 項目,例如,您的函式處理要在 XML 文件註解中描述的特定 XML 項目,您可以使用標準引號機制 (&lt;&gt;)。 若要參照程式碼參考 (cref) 項目中的泛型識別碼,您可以使用逸出字元 (例如 cref="List&lt;T&gt;") 或大括號 (cref="List{T}")。 視為特殊案例的情形是,編譯器將大括號剖析為角括號,如此在參考泛型識別項時,文件註解撰寫起來就變得不那麼複雜。

注意

XML 文件註解並不是中繼資料,這些註解不會包含在編譯的組件內,因此不能透過反映存取。

接受 XML 文件輸入的工具

下列工具會從 XML 註解建立輸出:

  • DocFXDocFX 是 .NET 的 API 文件產生器,目前支援 C#、Visual Basic 和 F#。 其也可讓您自訂產生的參考文件。 DocFX 會從原始程式碼和 Markdown 檔案建置靜態 HTML 網站。 此外,DocFX 可讓您彈性地透過範本自訂網站的配置和樣式。 您還可以建立自訂範本。
  • SandcastleSandcastle 工具會為包含概念和 API 參考頁面的受控類別庫建立說明檔案。 Sandcastle 工具是以命令列為基礎,沒有 GUI 前端、專案管理功能或自動化建置處理序。 Sandcastle 說明檔案產生器提供獨立 GUI 和命令列式工具,以自動化方式建置說明檔案。 Visual Studio 整合套件也可供使用,以便從 Visual Studio 內完整建立和管理說明專案。
  • DoxygenDoxygen 會從一組記載的來源檔案產生線上文件瀏覽器 (在 HTML),或離線參考手冊 (在 LaTeX 中)。 也支援在 RTF (MS Word)、PostScript、超連結 PDF、壓縮 HTML、DocBook 和 Unix 手冊頁產生輸出。 您可以設定 Doxygen,從未記載的來源檔案中擷取程式碼結構。

識別碼字串

每個型別或成員都會儲存在輸出 XML 檔案的元素中。 每個元素都有可識別型別或成員的唯一識別碼字串。 識別碼字串必須考慮運算子、參數、傳回值、泛型型別參數、refinout 參數。 為了編碼所有這些潛在元素,編譯器會遵循明確定義的規則來產生識別碼字串。 處理 XML 檔案的程式可以使用識別碼字串,來識別適用於該文件的對應 .NET 中繼資料或反映項目。

編譯器在產生識別碼字串時會遵守下列規則:

  • 字串中沒有空白字元。

  • 識別碼字串的第一個部分會識別成員種類,格式為單一字元後面接著一個冒號。 使用的成員類型如下:

    字元 成員類型 備註
    N 命名空間 您無法將文件註解新增至命名空間,但可讓 cref 參考這些項目 (如果支援)。
    T type 型別是類別、介面、結構、列舉或委派。
    F field
    P property 包含索引子或其他索引屬性。
    M method 包含特殊方法,例如建構函式和運算子。
    E event
    ! 錯誤字串 字串的其餘部分提供與錯誤相關的資訊。 C# 編譯器會針對無法解析的連結產生錯誤資訊。
  • 字串的第二個部分是項目的完整名稱 (從命名空間的根開始)。 項目名稱、其封入類型及命名空間會以句號來分隔。 如果項目名稱本身包含句點,則會以雜湊符號 ('#') 來加以取代。 假設沒有項目的名稱中直接含有雜湊符號。 例如,String 建構函式的完整名稱會是 "System.String.#ctor"。

  • 對於屬性和方法,以括弧括住的參數清單如下。 如果沒有任何參數,就不會出現括弧。 以逗號分隔參數。 每個參數的編碼會直接遵循在 .NET 簽章中編碼的方式 (請參閱 Microsoft.VisualStudio.CorDebugInterop.CorElementType,以了解下列清單中所有大寫元素的定義):

    • 基底類型。 一般型別 (ELEMENT_TYPE_CLASSELEMENT_TYPE_VALUETYPE) 會以型別的完整名稱表示。
    • 內建型別 (例如ELEMENT_TYPE_I4ELEMENT_TYPE_OBJECTELEMENT_TYPE_STRINGELEMENT_TYPE_TYPEDBYREFELEMENT_TYPE_VOID) 會以對應之完整型別的完整名稱表示。 例如,System.Int32System.TypedReference
    • ELEMENT_TYPE_PTR 會表示為 '*',緊接在已修改的型別之後。
    • ELEMENT_TYPE_BYREF 會表示為 ''@'',緊接在已修改的型別之後。
    • ELEMENT_TYPE_CMOD_OPT 會表示為 '!' 和修飾元類別的完整名稱,緊接在已修改的型別之後。
    • ELEMENT_TYPE_SZARRAY 會表示為 "[]",緊接在陣列的元素型別之後。
    • ELEMENT_TYPE_ARRAY 會表示為 [lower bound:size,lower bound:size],其中逗號數目的順位是 -1,而每個維度的下限和大小 (如果已知) 會以十進位格式表示。 如果未指定下限或大小,就會加以省略。 如果省略了特定維度的下限和大小,則也會省略 ':'。 例如,下限為 1 且未指定大小的雙維度陣列是 [1:,1:]。
  • 僅針對轉換運算子 (op_Implicitop_Explicit),此方法的傳回值會編碼為 '~',後面接著傳回型別。 例如:<member name="M:System.Decimal.op_Explicit(System.Decimal arg)~System.Int32">System.Decimal 類別中宣告之轉換運算子 public static explicit operator int (decimal value); 的標籤。

  • 針對泛型類型,類型的名稱後面會接著反引號,然後是表示泛型類型參數數目的數字。 例如:<member name="T:SampleClass`2"> 是定義為 public class SampleClass<T, U> 的型別標籤。 針對接受泛型型別作為參數的方法,會將泛型型別參數指定為前面加上反引號的數字 (例如 `0、`1)。 每個數字都表示型別泛型參數之以零為起始的陣列標記法。

    • ELEMENT_TYPE_PINNED 會表示為 ''^'',緊接在已修改的型別之後。 C# 編譯器永遠都不會產生這個編碼。
    • ELEMENT_TYPE_CMOD_REQ 會表示為 '|' 和修飾元類別的完整名稱,緊接在已修改的型別之後。 C# 編譯器永遠都不會產生這個編碼。
    • ELEMENT_TYPE_GENERICARRAY 會表示為 "[?]",緊接在陣列的元素型別之後。 C# 編譯器永遠都不會產生這個編碼。
    • ELEMENT_TYPE_FNPTR 會表示為 "=FUNC:type(signature)",其中 type 是傳回型別,而 signature 是方法的引數。 如果沒有任何引數,就會省略括弧。 C# 編譯器永遠都不會產生這個編碼。
    • 不會表示下列簽章元件,因為其不會用來區分多載方法:
      • 呼叫慣例
      • 傳回類型
      • ELEMENT_TYPE_SENTINEL

下列範例顯示針對類別及其成員產生識別碼字串的方式:

namespace MyNamespace;

/// <summary>
/// Enter description here for class X.
/// ID string generated is "T:MyNamespace.MyClass".
/// </summary>
public unsafe class MyClass
{
    /// <summary>
    /// Enter description here for the first constructor.
    /// ID string generated is "M:MyNamespace.MyClass.#ctor".
    /// </summary>
    public MyClass() { }

    /// <summary>
    /// Enter description here for the second constructor.
    /// ID string generated is "M:MyNamespace.MyClass.#ctor(System.Int32)".
    /// </summary>
    /// <param name="i">Describe parameter.</param>
    public MyClass(int i) { }

    /// <summary>
    /// Enter description here for field Message.
    /// ID string generated is "F:MyNamespace.MyClass.Message".
    /// </summary>
    public string? Message;

    /// <summary>
    /// Enter description for constant PI.
    /// ID string generated is "F:MyNamespace.MyClass.PI".
    /// </summary>
    public const double PI = 3.14;

    /// <summary>
    /// Enter description for method Func.
    /// ID string generated is "M:MyNamespace.MyClass.Func".
    /// </summary>
    /// <returns>Describe return value.</returns>
    public int Func() => 1;

    /// <summary>
    /// Enter description for method SomeMethod.
    /// ID string generated is "M:MyNamespace.MyClass.SomeMethod(System.String,System.Int32@,System.Void*)".
    /// </summary>
    /// <param name="str">Describe parameter.</param>
    /// <param name="num">Describe parameter.</param>
    /// <param name="ptr">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public int SomeMethod(string str, ref int nm, void* ptr) { return 1; }

    /// <summary>
    /// Enter description for method AnotherMethod.
    /// ID string generated is "M:MyNamespace.MyClass.AnotherMethod(System.Int16[],System.Int32[0:,0:])".
    /// </summary>
    /// <param name="array1">Describe parameter.</param>
    /// <param name="array">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public int AnotherMethod(short[] array1, int[,] array) { return 0; }

    /// <summary>
    /// Enter description for operator.
    /// ID string generated is "M:MyNamespace.MyClass.op_Addition(MyNamespace.MyClass,MyNamespace.MyClass)".
    /// </summary>
    /// <param name="first">Describe parameter.</param>
    /// <param name="second">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public static MyClass operator +(MyClass first, MyClass second) { return first; }

    /// <summary>
    /// Enter description for property.
    /// ID string generated is "P:MyNamespace.MyClass.Prop".
    /// </summary>
    public int Prop { get { return 1; } set { } }

    /// <summary>
    /// Enter description for event.
    /// ID string generated is "E:MyNamespace.MyClass.OnHappened".
    /// </summary>
    public event Del? OnHappened;

    /// <summary>
    /// Enter description for index.
    /// ID string generated is "P:MyNamespace.MyClass.Item(System.String)".
    /// </summary>
    /// <param name="str">Describe parameter.</param>
    /// <returns></returns>
    public int this[string s] => 1;

    /// <summary>
    /// Enter description for class Nested.
    /// ID string generated is "T:MyNamespace.MyClass.Nested".
    /// </summary>
    public class Nested { }

    /// <summary>
    /// Enter description for delegate.
    /// ID string generated is "T:MyNamespace.MyClass.Del".
    /// </summary>
    /// <param name="i">Describe parameter.</param>
    public delegate void Del(int i);

    /// <summary>
    /// Enter description for operator.
    /// ID string generated is "M:MyNamespace.MyClass.op_Explicit(MyNamespace.MyClass)~System.Int32".
    /// </summary>
    /// <param name="myParameter">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public static explicit operator int(MyClass myParameter) => 1;
}

C# 語言規格

如需詳細資訊,請參閱文件註解的 C# 語言規格附錄。