通过


教程:创建 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,则可以使用“生成”属性页启用此功能。

构建项目。 编译器生成一个 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 注释创建输出来探索详细信息:

  • 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 配置为从没有文档记录的源文件中提取代码结构。