教學課程:建立 XML 文件

在本教學課程中,您會取得現有的物件導向範例 (來自上一個 教學課程),並使用 XML 文件註解來增強它。 XML 檔註解提供實用的 IntelliSense 工具提示,而且可以參與產生的 API 參考檔。您將了解哪些元素值得註釋,如何使用核心標籤,<summary>例如 、 <param><returns><value><remarks><example><seealso><exception><inheritdoc>和 ,以及一致、有目的的註解如何提高可維護性、可發現性和協作性,而不會增加噪音。 最後,您已對範例的公用介面進行了批註,編譯專案以生成 XML 文件,並查看這些註解如何直接流入開發人員體驗和下游文件工具。

在本教學課程中,您會:

  • 在您的 C# 專案中啟用 XML 文件輸出。
  • 將 XML 註解新增至類型和成員,並結構化這些註解。
  • 建置專案並檢查產生的 XML 文件檔案。

先決條件

啟用 XML 文件

載入您在上述 物件導向教學課程中建置的專案。 如果您想要重新開始,請從資料夾下的dotnet/docs存放庫複製snippets/object-oriented-programming範例。

接下來,啟用 XML 文件輸出,讓編譯器在元件旁邊發出檔案 .xml 。 編輯專案檔,並在元素內 <PropertyGroup> 新增 (或確認) 下列屬性:

<GenerateDocumentationFile>True</GenerateDocumentationFile>

如果您使用 Visual Studio,您可以使用 “build” 屬性頁面來啟用此功能。

建置專案。 編譯器會生成一個 XML 檔案,聚集了所有來自公開可見類型和成員的 /// 註解。 該檔案提供 IntelliSense 工具提示、靜態分析工具和下游文件產生系統。

立即建置專案。 您會看到任何公用成員遺漏 <summary> 註解的警告。 將這些警告視為待辦事項清單,以協助您完成完整且有意的文件編寫。 開啟產生的 XML 檔案 (它位於建置輸出旁邊) 並檢查初始結構。 一開始,該 <members> 部分是空的,因為您尚未添加註釋:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>oo-programming</name>
    </assembly>
    <members>
    </members>
</doc>

檔案就位後,開始新增目標 XML 註解,並立即驗證每個註解在產生的輸出中的顯示方式。 從 Transaction 記錄類型開始:

namespace OOProgramming;

/// <summary>
/// Represents an immutable financial transaction with an amount, date, and descriptive notes.
/// </summary>
/// <param name="Amount">The transaction amount. Positive values represent credits/deposits, negative values represent debits/withdrawals.</param>
/// <param name="Date">The date and time when the transaction occurred.</param>
/// <param name="Notes">Descriptive notes or memo text associated with the transaction.</param>
public record Transaction(decimal Amount, DateTime Date, string Notes);

新增文件註解

您現在會逐一查看建置警告,以將簡潔、有用的文件新增至 BankAccount 類型。 每個警告都會指出公用成員缺少 <summary>(或其他必要)元素。 將警告清單視為清單。 避免新增雜訊:專注於描述意圖、不變量和重要的使用條件約束,略過重述明顯的類型名稱或參數類型。

  1. 再次建置專案。 在 Visual Studio 或 Visual Studio Code 中,開啟 [錯誤清單/問題] 面板,並篩選檔警告 (CS1591)。 在命令列中,執行組建並檢閱發出至主控台的警告。
  2. 導覽至第一個警告 (類別 BankAccount )。 在宣告上方的行中,輸入 ///。 編輯器搭建了一個 <summary> 元素。 將佔位符替換為一個以動作為中心的句子。 這句話解釋了帳戶在域中的角色。 例如,它追蹤交易並強制執行最低餘額。
  3. 僅當您需要解釋行為時才添加 <remarks> 。 範例包括最低餘額強制執行的運作方式或帳號的產生方式。 保持簡短的評論。
  4. 針對每個屬性(NumberOwnerBalance),輸入///,並撰寫<summary>,描述該值代表的是什麼,而不是描述簡單的 getter 如何傳回該值。 如果屬性計算值 (例如 Balance),請新增一個 <value> 元素來釐清計算。
  5. 對於每個建構函式,添加描述每個參數意義的 <summary><param> 元素,而不僅僅重述參數名稱。 如果有一個重載委派給另一個重載,請添加一個簡潔的 <remarks> 元素。
  6. 對於可以擲回的方法,請為每個有意的例外類型新增 <exception> 標籤。 描述觸發它的條件。 請勿記錄引數驗證協助程式擲回的例外狀況,除非它們是公用合約的一部分。
  7. 對於傳回值的方法,請新增 <returns>,並提供一個簡短的描述來說明呼叫者接收到的資訊。 避免重複方法名稱或受控類型。
  8. 先使用 BankAccount 基類。

你的版本應該會像以下程式碼這樣:

namespace OOProgramming;

/// <summary>
/// Represents a bank account with basic banking operations including deposits, withdrawals, and transaction history.
/// Supports minimum balance constraints and provides extensible month-end processing capabilities.
/// </summary>
public class BankAccount
{
    /// <summary>
    /// Gets the unique account number for this bank account.
    /// </summary>
    /// <value>A string representation of the account number, generated sequentially.</value>
    public string Number { get; }
    
    /// <summary>
    /// Gets or sets the name of the account owner.
    /// </summary>
    /// <value>The full name of the person who owns this account.</value>
    public string Owner { get; set; }
    
    /// <summary>
    /// Gets the current balance of the account by calculating the sum of all transactions.
    /// </summary>
    /// <value>The current account balance as a decimal value.</value>
    public decimal Balance => _allTransactions.Sum(i => i.Amount);

    private static int s_accountNumberSeed = 1234567890;

    private readonly decimal _minimumBalance;

    /// <summary>
    /// Initializes a new instance of the BankAccount class with the specified owner name and initial balance.
    /// Uses a default minimum balance of 0.
    /// </summary>
    /// <param name="name">The name of the account owner.</param>
    /// <param name="initialBalance">The initial deposit amount for the account.</param>
    /// <remarks>
    /// This constructor is a convenience overload that calls the main constructor with a minimum balance of 0.
    /// If the initial balance is greater than 0, it will be recorded as the first transaction with the note "Initial balance".
    /// The account number is automatically generated using a static seed value that increments for each new account.
    /// </remarks>
    public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }

    /// <summary>
    /// Initializes a new instance of the BankAccount class with the specified owner name, initial balance, and minimum balance constraint.
    /// </summary>
    /// <param name="name">The name of the account owner.</param>
    /// <param name="initialBalance">The initial deposit amount for the account.</param>
    /// <param name="minimumBalance">The minimum balance that must be maintained in the account.</param>
    /// <remarks>
    /// This is the primary constructor that sets up all account properties. The account number is generated automatically
    /// using a static seed value. If an initial balance is provided and is greater than 0, it will be added as the first
    /// transaction. The minimum balance constraint will be enforced on all future withdrawal operations through the
    /// <see cref="CheckWithdrawalLimit"/> method.
    /// </remarks>
    public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
    {
        Number = s_accountNumberSeed.ToString();
        s_accountNumberSeed++;

        Owner = name;
        _minimumBalance = minimumBalance;
        if (initialBalance > 0)
            MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
    }

    private readonly List<Transaction> _allTransactions = [];

    /// <summary>
    /// Makes a deposit to the account by adding a positive transaction.
    /// </summary>
    /// <param name="amount">The amount to deposit. Must be positive.</param>
    /// <param name="date">The date when the deposit is made.</param>
    /// <param name="note">A descriptive note about the deposit transaction.</param>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the deposit amount is zero or negative.</exception>
    /// <remarks>
    /// This method creates a new <see cref="Transaction"/> object with the specified amount, date, and note,
    /// then adds it to the internal transaction list. The transaction amount must be positive - negative amounts
    /// are not allowed for deposits. The account balance is automatically updated through the Balance property
    /// which calculates the sum of all transactions. There are no limits or restrictions on deposit amounts.
    /// </remarks>
    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
        }
        var deposit = new Transaction(amount, date, note);
        _allTransactions.Add(deposit);
    }

    /// <summary>
    /// Makes a withdrawal from the account by adding a negative transaction.
    /// Checks withdrawal limits and minimum balance constraints before processing.
    /// </summary>
    /// <param name="amount">The amount to withdraw. Must be positive.</param>
    /// <param name="date">The date when the withdrawal is made.</param>
    /// <param name="note">A descriptive note about the withdrawal transaction.</param>
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the withdrawal amount is zero or negative.</exception>
    /// <exception cref="InvalidOperationException">Thrown when the withdrawal would cause the balance to fall below the minimum balance.</exception>
    /// <remarks>
    /// This method first validates that the withdrawal amount is positive, then checks if the withdrawal would
    /// violate the minimum balance constraint by calling <see cref="CheckWithdrawalLimit"/>. The withdrawal is
    /// recorded as a negative transaction amount. If the withdrawal limit check returns an overdraft transaction
    /// (such as a fee), that transaction is also added to the account. The method enforces business rules through
    /// the virtual CheckWithdrawalLimit method, allowing derived classes to implement different withdrawal policies.
    /// </remarks>
    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
        }
        Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
        Transaction? withdrawal = new(-amount, date, note);
        _allTransactions.Add(withdrawal);
        if (overdraftTransaction != null)
            _allTransactions.Add(overdraftTransaction);
    }

    /// <summary>
    /// Checks whether a withdrawal would violate the account's minimum balance constraint.
    /// This method can be overridden in derived classes to implement different withdrawal limit policies.
    /// </summary>
    /// <param name="isOverdrawn">True if the withdrawal would cause the balance to fall below the minimum balance.</param>
    /// <returns>A Transaction object representing any overdraft fees or penalties, or null if no additional charges apply.</returns>
    /// <exception cref="InvalidOperationException">Thrown when the withdrawal would cause an overdraft and the account type doesn't allow it.</exception>
    protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
    {
        if (isOverdrawn)
        {
            throw new InvalidOperationException("Not sufficient funds for this withdrawal");
        }
        else
        {
            return default;
        }
    }

    /// <summary>
    /// Generates a detailed account history report showing all transactions with running balance calculations.
    /// </summary>
    /// <returns>A formatted string containing the complete transaction history with dates, amounts, running balances, and notes.</returns>
    /// <remarks>
    /// This method creates a formatted report that includes a header row followed by all transactions in chronological order.
    /// Each row shows the transaction date (in short date format), the transaction amount, the running balance after that
    /// transaction, and any notes associated with the transaction. The running balance is calculated by iterating through
    /// all transactions and maintaining a cumulative total. The report uses tab characters for column separation and
    /// is suitable for display in console applications or simple text outputs.
    /// </remarks>
    public string GetAccountHistory()
    {
        var report = new System.Text.StringBuilder();

        decimal balance = 0;
        report.AppendLine("Date\t\tAmount\tBalance\tNote");
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
            report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
        }

        return report.ToString();
    }

    /// <summary>
    /// Performs month-end processing for the account. This virtual method can be overridden in derived classes
    /// to implement specific month-end behaviors such as interest calculations, fee assessments, or statement generation.
    /// </summary>
    /// <remarks>
    /// The base implementation of this method does nothing, providing a safe default for basic bank accounts.
    /// Derived classes such as savings accounts or checking accounts can override this method to implement
    /// account-specific month-end processing. Examples include calculating and applying interest payments,
    /// assessing monthly maintenance fees, generating account statements, or performing regulatory compliance checks.
    /// This method is typically called by banking systems at the end of each month as part of batch processing operations.
    /// </remarks>
    public virtual void PerformMonthEndTransactions() { }
}

完成後,請開啟重新產生的 XML 檔案,並確認每個成員都與您的新元素一起顯示。 修剪後的部分可能如下所示:

<member name="T:OOProgramming.BankAccount">
  <summary>Represents a bank account that records transactions and enforces an optional minimum balance.</summary>
  <remarks>Account numbers are generated sequentially when each instance is constructed.</remarks>
</member>
<member name="P:OOProgramming.BankAccount.Balance">
  <summary>Gets the current balance based on all recorded transactions.</summary>
  <value>The net sum of deposits and withdrawals.</value>
</member>

小提示

將摘要保留為一句話。 如果您需要多個內容,請將次要內容移至 <remarks>

<inheritdoc/>在衍生類別中使用

如果您從 BankAccount 衍生(例如:適用利息的 SavingsAccount),可以繼承基礎文件,而不是複製它。 在衍生成員的文件區塊內新增自動關閉 <inheritdoc/> 元素。 你仍然可以在<inheritdoc/>之後加上更多元素(例如額外<remarks>細節)來記錄專門的行為:

/// <inheritdoc/>
/// <remarks>
/// An interest-earning account is a specialized savings account that rewards customers for maintaining higher balances.
/// Interest is only earned when the account balance exceeds $500, encouraging customers to maintain substantial deposits.
/// The annual interest rate of 2% is applied monthly to qualifying balances, providing a simple savings incentive.
/// This account type uses the standard minimum balance of $0 from the base <see cref="BankAccount"/> class.
/// </remarks>
public class InterestEarningAccount : BankAccount

備註

<inheritdoc/> 減少重複,並在稍後更新基底類型文件時協助保持一致性。

撰寫完公用介面文件後,請進行最終建置,以確認沒有剩餘的 CS1591 警告。 您的專案現在會產生有用的 IntelliSense 和結構化 XML 檔案,可供發佈工作流程。

你可以在 GitHub 的 dotnet/docs 倉庫的原始碼資料夾看到完整的註解範例。

透過註解生成輸出

您可以嘗試以下任何工具來從 XML 註解建立輸出,以探索更多內容:

  • DocFX:DocFX 是 .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 以從未記載的原始檔中擷取程式碼結構。