Esercitazione: Creare la documentazione XML

In questa esercitazione si prende un esempio orientato agli oggetti esistente ( dall'esercitazione precedente) e lo si migliora con i commenti della documentazione XML. I commenti della documentazione XML forniscono utili descrizioni comandi di IntelliSense e possono partecipare alla documentazione di riferimento API generata. Si apprenderà quali elementi devono essere commentati, come usare tag di base come <summary>, <param>, <returns>, <value>, <remarks>, <example>, <seealso>, <exception> e <inheritdoc> e come un commento coerente e intenzionale migliora la gestibilità, l'individuabilità e la collaborazione, senza aggiungere rumore. Alla fine, è stata annotata la superficie pubblica dell'esempio, è stato compilato il progetto per generare il file di documentazione XML e si è visto come questi commenti vengono trasmessi direttamente all'esperienza di sviluppo e agli strumenti per la documentazione downstream.

In questa esercitazione, farai:

  • Abilitare l'output della documentazione XML nel progetto C#.
  • Aggiungere e strutturare commenti della documentazione XML ai tipi e ai membri.
  • Compilare il progetto ed esaminare il file di documentazione XML generato.

Prerequisiti

Abilitare la documentazione XML

Caricare il progetto compilato nell'esercitazione precedente orientata agli oggetti. Se si preferisce iniziare da zero, clonare dal repository l'esempio dotnet/docs nella cartella snippets/object-oriented-programming.

Abilita quindi l'output della documentazione XML affinché il compilatore generi un file .xml insieme all'assembly. Modificare il file di progetto e aggiungere (o confermare) la proprietà seguente all'interno di un <PropertyGroup> elemento :

<GenerateDocumentationFile>True</GenerateDocumentationFile>

Se si usa Visual Studio, è possibile abilitare questa opzione usando la pagina delle proprietà "build".

Costruisci il progetto. Il compilatore genera un file XML che aggrega tutti i /// commenti di tipi e membri visibili pubblicamente. Quel file alimenta i tooltip di IntelliSense, i tool di analisi statica e i sistemi di generazione della documentazione a valle.

Compilare ora il progetto. Vengono visualizzati avvisi per qualsiasi membro pubblico che manchi dei commenti <summary>. Considerate tali avvisi come un elenco di cose da fare che vi aiuta a fornire una documentazione completa e deliberata. Aprire il file XML generato (accanto all'output di compilazione) ed esaminare la struttura iniziale. In un primo momento, la <members> sezione è vuota perché non sono stati ancora aggiunti commenti:

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

Con il file sul posto, iniziare ad aggiungere commenti XML di destinazione e verificare immediatamente la modalità di visualizzazione di ognuno nell'output generato. Inizia con il tipo di record 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);

Aggiungere commenti alla documentazione

È ora possibile scorrere gli avvisi di compilazione per aggiungere documentazione concisa e utile al BankAccount tipo. Ogni avviso indica un membro pubblico che non dispone di un <summary> elemento (o un altro elemento obbligatorio). Considerare l'elenco di avvisi come elenco di controllo. Evitare di aggiungere rumore: concentrarsi sulla descrizione delle finalità, degli invarianti e dei vincoli di utilizzo importanti, evitando di ripetere nomi di tipi o tipi di parametri ovvi.

  1. Compilare di nuovo il progetto. In Visual Studio o Visual Studio Code aprire il pannello Elenco errori/Problemi e filtrare gli avvisi della documentazione (CS1591). Nella riga di comando eseguire una compilazione ed esaminare gli avvisi generati nella console.
  2. Passare al primo avviso (la classe BankAccount). Nella riga sopra la dichiarazione digitare ///. L'editor svolge un'operazione di scaffolding di un <summary> elemento. Sostituire il segnaposto con una singola frase incentrata sull'azione. La frase illustra il ruolo dell'account nel dominio. Ad esempio, tiene traccia delle transazioni e applica un saldo minimo.
  3. Aggiungere <remarks> solo se è necessario spiegare il comportamento. Gli esempi includono il funzionamento dell'imposizione minima del saldo o il modo in cui vengono generati i numeri di conto. Tenere brevi le osservazioni.
  4. Per ogni proprietà (Number, Owner, Balance), digitare /// e scrivere un <summary> oggetto che indica ciò che il valore rappresenta, non il modo in cui un getter semplice lo restituisce. Se una proprietà calcola un valore (ad esempio Balance), aggiungere un <value> elemento che chiarisca il calcolo.
  5. Per ogni costruttore, aggiungere <summary> più <param> elementi che descrivono il significato di ogni argomento, non solo riformulare il nome del parametro. Se un sovraccarico delega a un altro, aggiungere un elemento conciso <remarks>.
  6. Per i metodi che possono lanciare, aggiungere un tag <exception> per ogni tipo di eccezione intenzionale. Descrivere la condizione che lo attiva. Non documentare le eccezioni generate dagli helper di convalida degli argomenti a meno che non facciano parte del contratto pubblico.
  7. Per i metodi che restituiscono un valore, aggiungere <returns> con una breve descrizione di ciò che riceve il chiamante. Evitare di ripetere il nome del metodo o il tipo gestito.
  8. Usare prima la BankAccount classe di base.

La versione dovrebbe essere simile al codice seguente:

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() { }
}

Al termine, aprire il file XML rigenerato e verificare che ogni membro venga visualizzato con i nuovi elementi. Una parte tagliata potrebbe essere simile alla seguente:

<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>

Suggerimento

Mantenere i riepiloghi in una singola frase. Se hai bisogno di più di uno, sposta il contesto secondario in <remarks>.

Uso <inheritdoc/> nelle classi derivate

Se si deriva da BankAccount (ad esempio, un oggetto SavingsAccount che applica interesse), è possibile ereditare la documentazione di base anziché copiarla. Aggiungi un elemento autochiudente <inheritdoc/> all'interno del blocco di documentazione del membro derivato. È comunque possibile aggiungere altri elementi (ad esempio dettagli aggiuntivi <remarks> ) dopo <inheritdoc/> per documentare il comportamento specializzato:

/// <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

Annotazioni

<inheritdoc/> riduce la duplicazione e consente di mantenere la coerenza quando si aggiorna la documentazione del tipo di base in un secondo momento.

Dopo aver completato la documentazione della superficie pubblica, effettuare l'ultima compilazione per confermare che non siano presenti avvisi CS1591 rimanenti. Il progetto produce ora utili IntelliSense e un file XML strutturato pronto per la pubblicazione dei flussi di lavoro.

È possibile visualizzare l'esempio con annotazioni completo nella cartella di origine del repository dotnet/docs in GitHub.

Costruire l'output attraverso i commenti

È possibile esplorare di più provando uno di questi strumenti per creare l'output dai commenti XML:

  • DocFX: DocFX è un generatore di documentazione API per .NET, che attualmente supporta C#, Visual Basic e F#. Consente inoltre di personalizzare la documentazione di riferimento generata. DocFX compila un sito Web HTML statico dal codice sorgente e dai file Markdown. Inoltre, DocFX offre la flessibilità necessaria per personalizzare il layout e lo stile del sito Web tramite modelli. È anche possibile creare modelli personalizzati.
  • Sandcastle: gli strumenti Sandcastle creano file della Guida per le librerie di classi gestite contenenti sia pagine di riferimento concettuali che API. Gli strumenti Sandcastle sono basati sulla riga di comando e non dispongono di funzionalità front-end gui, gestione dei progetti o processo di compilazione automatizzato. Sandcastle Help File Builder fornisce un'interfaccia utente grafica autonoma e strumenti da riga di comando per creare un file della Guida in modo automatizzato. È disponibile anche un pacchetto di integrazione per Visual Studio per consentire che i progetti della Guida possano essere creati e gestiti interamente all'interno di Visual Studio.
  • Doxygen: Doxygen genera un browser di documentazione online (in HTML) o un manuale di riferimento offline (in LaTeX) da un set di file di origine documentati. È disponibile anche il supporto per la generazione di output in formato RTF (MS Word), PostScript, PDF con collegamenti ipertestuali, HTML compresso, DocBook e pagine manuali Unix. È possibile configurare Doxygen per estrarre la struttura del codice dai file di origine non documentati.