code documentation - software development - C# -

C# Documentation: XML Comments, Examples, and Build Checks

Learn C# documentation with practical XML comment examples, recommended tags, compiler settings, warning checks, and a maintainable publishing workflow.

Written by DocuWriter.ai Reviewed by DocuWriter Editorial Team on September 7, 2026

How C# documentation comments work

C# documentation comments beside source code

C# documentation comments are XML fragments placed immediately before a type or member. They begin with /// for a single-line comment block or /** for a delimited block. When XML documentation output is enabled, the compiler writes the comments to an XML file that documentation tools and IDEs can read.

The official C# documentation comments specification defines the processing rules and recommended tags. Two details matter in day-to-day work:

  • the comment must immediately precede the class, interface, method, property, field, delegate, or event it describes;
  • the XML must be well formed, and references in tags such as <param> and cref can be checked by the documentation generator.

This is the smallest useful C# documentation example:

/// <summary>
/// Calculates the total after applying a percentage discount.
/// </summary>
/// <param name="subtotal">The amount before the discount.</param>
/// <param name="discountPercent">A value from 0 through 100.</param>
/// <returns>The discounted total.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="discountPercent"/> is outside 0 through 100.
/// </exception>
public static decimal ApplyDiscount(decimal subtotal, decimal discountPercent)
{
    if (discountPercent is < 0 or > 100)
    {
        throw new ArgumentOutOfRangeException(nameof(discountPercent));
    }

    return subtotal * (1 - discountPercent / 100);
}

The comment answers questions a caller cannot settle from the signature alone: the accepted range, the meaning of the return value, and the exception raised for invalid input. Repeating “applies a discount” in every tag would add volume without helping the reader.

The C# specification recommends a standard set of tags. You do not need all of them on every member.

TagUse it forCommon mistake
<summary>A concise description shown by IntelliSenseRestating the member name without explaining behavior
<param>The meaning, units, limits, or format of a parameterWriting only “the value”
<returns>The returned value and relevant statesOmitting null, empty, or sentinel cases
<exception>An exception callers should handleListing exceptions the method cannot throw
<remarks>Details that do not fit the summaryHiding critical constraints in a long paragraph
<example>A short, representative usage exampleProviding code that no longer compiles
<see> / <seealso>A checked reference to another code element or resourcePasting an ambiguous plain-text type name
<typeparam>The role or constraint of a generic type parameterRepeating the generic parameter name
<inheritdoc />Documentation inherited from a base member or interfaceInheriting text when the implementation changes the contract

Use cref and paramref for checked references

XML documentation can point to code elements instead of relying on prose that may drift:

/// <summary>
/// Loads an order with <see cref="IOrderRepository"/>.
/// </summary>
/// <param name="orderId">
/// The identifier passed to <see cref="IOrderRepository.FindAsync"/>.
/// </param>
public Task<Order?> LoadAsync(Guid orderId)

The generator can warn when a cref target or <param> name is invalid. That makes these tags safer than manually typing “see the FindAsync method” into a paragraph.

Put the contract in comments, not the implementation

Write what a caller needs to know:

  • accepted values and units;
  • observable side effects;
  • authorization or state requirements;
  • return states;
  • exceptions the caller can respond to;
  • ordering, retry, or concurrency behavior when it affects use.

Do not narrate every line of the method. A comment such as “loops through orders and adds each amount” becomes false after a refactor and reveals nothing that the code does not already show.

Enable XML documentation output

Add GenerateDocumentationFile to the project file:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Then run:

dotnet build

The build writes an XML documentation file beside the compiled assembly. Exact output paths depend on the target framework and build configuration.

Once generation is enabled, a project with undocumented public members may produce CS1591 warnings. Do not immediately suppress the warning across the entire project. First decide what the public API is.

Choose the surface you intend to document

For a reusable package, public types and members are the customer-facing API and usually deserve complete C# documentation. For an application, many public classes exist only because of framework conventions. Teams can still document them, but the useful unit may be the module, endpoint, job, or workflow rather than every property.

A practical policy is:

  1. document externally consumed public APIs strictly;
  2. document non-obvious internal contracts and failure modes;
  3. avoid comments that merely translate syntax into English;
  4. treat missing documentation warnings as errors only in the projects where the policy is appropriate.

If you need a narrow suppression, explain its scope in the project configuration or analyzer rules. A blanket NoWarn entry can make the build quiet while leaving the API unexplained.

A complete C# documentation example

The following interface shows how the common tags work together:

public interface IInvoiceService
{
    /// <summary>
    /// Creates a draft invoice for an existing customer.
    /// </summary>
    /// <param name="customerId">The customer that will own the invoice.</param>
    /// <param name="lines">One or more invoice lines in display order.</param>
    /// <param name="cancellationToken">Cancels the pending operation.</param>
    /// <returns>The persisted draft, including its generated identifier.</returns>
    /// <exception cref="CustomerNotFoundException">
    /// No customer exists for <paramref name="customerId"/>.
    /// </exception>
    /// <exception cref="ArgumentException">
    /// <paramref name="lines"/> is empty or contains a non-positive quantity.
    /// </exception>
    Task<Invoice> CreateDraftAsync(
        Guid customerId,
        IReadOnlyList<InvoiceLine> lines,
        CancellationToken cancellationToken = default);
}

An implementation that preserves the interface contract can use <inheritdoc />:

public sealed class InvoiceService : IInvoiceService
{
    /// <inheritdoc />
    public async Task<Invoice> CreateDraftAsync(
        Guid customerId,
        IReadOnlyList<InvoiceLine> lines,
        CancellationToken cancellationToken = default)
    {
        // Implementation omitted.
    }
}

If the implementation adds an observable constraint, write a complete comment or add an accurate <remarks> section. Do not hide a changed contract behind inherited documentation.

Make C# documentation verifiable

Documentation comments sit close to the code, but proximity does not guarantee accuracy. Add checks that can fail before stale documentation ships.

Compile examples where possible

Move substantial examples into sample projects or tests, then reference them from the documentation. A copied code fence cannot tell you when a type is renamed or a constructor changes.

Build the documentation in CI

Run dotnet build with XML documentation enabled. If you publish reference pages with DocFX or another generator, build those pages in the same pipeline and fail on broken references.

Review public API changes with their comments

When a pull request changes a parameter, exception, return state, or side effect, update its XML documentation in the same review. A spelling-only review will not catch a contract mismatch.

Check the rendered output

Raw XML comments are source material. Before release, inspect the actual IntelliSense text or generated reference page. Look for broken links, collapsed paragraphs, malformed lists, missing type names, and examples that lost formatting.

XML comments are only one layer

XML documentation is strong at member-level reference. It is weak at explaining how several assemblies, services, queues, and data stores work together. A maintainable C# documentation set usually includes:

  • XML comments for public contracts and non-obvious behavior;
  • a README or quickstart for setup and the first successful run;
  • architecture pages for boundaries and dependencies;
  • API documentation for externally callable endpoints;
  • runbooks for deployment and failure recovery;
  • decision records for choices that source code cannot explain.

For more examples, see our guide to C# XML comments and these code documentation best practices.

DocuWriter can analyze a connected repository and generate a structured starting point across architecture, modules, services, classes, functions, dependencies, APIs, diagrams, and READMEs. Teams can centralize those pages in Spaces and use Autopilot to help keep them current. XML comments remain valuable source evidence, and a maintainer should review generated pages for business rules and intent that are not explicit in the code.

C# documentation checklist

Before merging a C# documentation change, check that:

  • each comment describes the public contract rather than the method body;
  • every parameter name matches the signature;
  • limits, units, nullable states, exceptions, and side effects are explicit;
  • cref links resolve;
  • examples compile against the current API;
  • the XML documentation file builds without unexpected warnings;
  • the rendered reference is readable;
  • broader architecture and workflow information lives outside member comments.

Good C# documentation is testable information attached to a real contract. Start with the members developers call most often, describe the decisions they need to make, and let the build catch what it can.