在本教學課程中,您會取得現有的物件導向範例 (來自上一個 教學課程),並使用 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>(或其他必要)元素。 將警告清單視為清單。 避免新增雜訊:專注於描述意圖、不變量和重要的使用條件約束,略過重述明顯的類型名稱或參數類型。
- 再次建置專案。 在 Visual Studio 或 Visual Studio Code 中,開啟 [錯誤清單/問題] 面板,並篩選檔警告 (CS1591)。 在命令列中,執行組建並檢閱發出至主控台的警告。
- 導覽至第一個警告 (類別
BankAccount)。 在宣告上方的行中,輸入///。 編輯器搭建了一個<summary>元素。 將佔位符替換為一個以動作為中心的句子。 這句話解釋了帳戶在域中的角色。 例如,它追蹤交易並強制執行最低餘額。 - 僅當您需要解釋行為時才添加
<remarks>。 範例包括最低餘額強制執行的運作方式或帳號的產生方式。 保持簡短的評論。 - 針對每個屬性(
Number,Owner,Balance),輸入///,並撰寫<summary>,描述該值代表的是什麼,而不是描述簡單的 getter 如何傳回該值。 如果屬性計算值 (例如Balance),請新增一個<value>元素來釐清計算。 - 對於每個建構函式,添加描述每個參數意義的
<summary>和<param>元素,而不僅僅重述參數名稱。 如果有一個重載委派給另一個重載,請添加一個簡潔的<remarks>元素。 - 對於可以擲回的方法,請為每個有意的例外類型新增
<exception>標籤。 描述觸發它的條件。 請勿記錄引數驗證協助程式擲回的例外狀況,除非它們是公用合約的一部分。 - 對於傳回值的方法,請新增
<returns>,並提供一個簡短的描述來說明呼叫者接收到的資訊。 避免重複方法名稱或受控類型。 - 先使用
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 還為您提供了通過模板自定義網站佈局和樣式的靈活性。 您也可以建立自訂範本。
- Sandcastle: Sandcastle 工具 會為包含概念和 API 參考頁面的受管理類別程式庫建立說明檔。 Sandcastle 工具是基於命令行的,沒有 GUI 前端、項目管理功能或自動化構建過程。 Sandcastle 說明檔案產生器提供獨立的 GUI 和命令列型工具,以自動化方式建置說明檔案。 Visual Studio 整合套件也可供其使用,以便完全從 Visual Studio 內建立和管理說明專案。
- Doxygen: Doxygen 從一組記錄的源文件生成在線文檔瀏覽器(HTML 格式)或離線參考手冊(LaTeX 格式)。 還支持在 RTF (MS Word)、PostScript、超鏈接 PDF、壓縮 HTML、DocBook 和 Unix 手冊頁中生成輸出。 您可以設定 Doxygen 以從未記載的原始檔中擷取程式碼結構。