C# XML Doc Comment Standard
How to write /// documentation comments for C#. The governing idea: <summary> is a short description of what a thing is; everything longer or different has a dedicated tag. Most verbosity problems are misplaced content, not bad writing.
Authoritative source: Microsoft C# reference, Recommended XML documentation tags and XML documentation comments.
Rules
<summary> is one declarative sentence: the "what." It appears in IntelliSense at every call site, so keep it tight. Microsoft: "Use the <summary> tag to describe a type or a type member." Do not pack rationale, design history, or parameter detail into it.
The "why" goes in <remarks>. Design intent, the role a type plays in the larger system, trade-offs, "this is the seam shared by X and Y" — all <remarks>. Microsoft: <remarks> "add information ... supplementing the information specified with <summary>. This tag can include more lengthy explanations."
Per-parameter and return detail goes in <param> / <returns>, never folded into the summary. The compiler validates <param name="..."> against the actual signature and warns on mismatch, so you get a free correctness check. One <param> per parameter.
Use the code-reference tags. <see cref="Type"/> for cross-references (DocFX/Sandcastle turn these into links and the compiler verifies the target exists), <paramref name="x"/> to refer to a parameter inside prose, <c>text</c> for inline code/identifiers, <code> for multi-line blocks.
Write complete sentences ending in a full stop. Microsoft states this explicitly. Applies to every tag's text, including <param> and <returns>.
Don't restate the signature or document the obvious. A summary of "Gets or sets the name." on a Name property adds nothing. Document the non-obvious: units, nullability meaning, invariants, side effects, ordering guarantees.
Use <inheritdoc/> instead of copy-pasting, and inherit in the right direction. The base owns the canonical docs; the derived member inherits. The interface (or base type) carries the real <summary>/<param>/<returns>; the implementation, override, or sync/async twin gets a bare <inheritdoc/>. A bare <inheritdoc/> on an implementation auto-resolves to the interface member it implements, so no cref is needed. Putting the prose on the implementation and pointing the interface at it with <inheritdoc cref="TheImplementation"/> is backwards: the contract should document itself, and if the implementation is undocumented the interface inherits nothing. Reserve <inheritdoc cref="..."/> for links the compiler can't infer, such as a sync/async twin. Note the asymmetry: Visual Studio auto-inherits docs into IntelliSense even without the tag, but the compiler-generated XML file does not — so for any library you distribute, write <inheritdoc/> explicitly or consumers get empty docs.
Generic and exception tags when they apply. <typeparam name="T"> for each type parameter; <exception cref="..."> for exceptions a caller can reasonably expect and handle.
Tag cheat-sheet
| Tag |
Holds |
<summary> |
One sentence: what this type/member is. |
<remarks> |
Why it exists, design intent, longer explanation. |
<param name="x"> |
What one parameter means. Compiler-validated. |
<returns> |
What the return value represents. |
<value> |
What a property's value represents. |
<typeparam name="T"> |
What a generic type parameter is for. |
<typeparamref name="T"/> |
Refers to a generic type parameter inside prose (generic analog of <paramref>). |
<exception cref="E"> |
An exception the member can throw. |
<see cref="M"/> |
Inline link to another code element. |
<paramref name="x"/> |
Refers to a parameter inside prose. |
<c> / <code> |
Inline / multi-line code formatting. |
<example> |
A usage example, usually wrapping a <code> block. |
<inheritdoc/> |
Inherit comments from base/interface/sync-twin. |
Examples
Good
/// <summary>
/// Resolves the target harness(es), runs their skill discovery, and returns the merged, ordered set.
/// </summary>
/// <remarks>
/// The reusable read-side seam shared by the <c>skills</c> listing command and skill sync, so neither
/// re-implements harness resolution or ordering. <see cref="ISkillCapability"/> is the data source;
/// this orchestrates over it and returns data only, never console markup.
/// </remarks>
internal sealed class SkillService { }
/// <summary>Discovers skills across the selected harness(es), ordered for stable display.</summary>
/// <param name="harnessId">Limit the query to one harness; <see langword="null"/> queries every harness that supports discovery.</param>
/// <param name="harnessHome">Overrides the config root.</param>
/// <param name="projectStart">Where to begin the project-scope walk-up; <see langword="null"/> skips project scope.</param>
/// <returns>The discovered skills ordered by scope then name, or a typed failure.</returns>
public SkillDiscoveryResult Discover(string? harnessId, string? harnessHome, string? projectStart) { }
Bad
/// <summary>
/// The read side of "push a local skill": resolve which harnesses to ask, fan out their skill
/// discovery, and return the merged, ordered set. This is the reusable logic seam shared by the
/// <c>skills</c> listing command and (later) skill sync, so neither re-implements harness resolution
/// or ordering. The harness capability is the data source; this is the orchestration over it.
/// Returns data only, never console markup.
/// </summary>
// ^ Rationale crammed into <summary>. IntelliSense shows this whole wall at every call site.
// Split: one-line <summary>, the rest to <remarks>.
/// <summary>
/// Discovers skills. harnessId limits to one harness (null = all); harnessHome overrides the config
/// root; projectStart is where to begin the project walk-up.
/// </summary>
// ^ Parameters described in prose instead of <param> tags — no compiler validation, harder to read.
Applying this
When asked to tighten verbose doc comments: keep the existing wording, just relocate it. Move design rationale from <summary> to <remarks>, lift per-parameter prose into <param> tags, and trim the summary to its first declarative sentence. Information is preserved, not deleted.
1---2name: csharp-doc-comments3description: House standard for writing C# XML documentation comments (/// triple-slash doc comments) to Microsoft's conventions. Use when writing, reviewing, or tightening <summary>, <remarks>, <param>, <returns>, <typeparam>, or <exception> tags on C# types and members, or when doc comments feel too verbose. Covers what belongs in summary vs remarks, when to use each tag, and the project rule that summaries stay short.4---5# C# XML Doc Comment Standard67How to write `///` documentation comments for C#. The governing idea: **`<summary>` is a short description of *what* a thing is; everything longer or different has a dedicated tag.** Most verbosity problems are misplaced content, not bad writing.89Authoritative source: Microsoft C# reference, [Recommended XML documentation tags](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/recommended-tags) and [XML documentation comments](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/).1011## Rules12131. **`<summary>` is one declarative sentence: the "what."** It appears in IntelliSense at every call site, so keep it tight. Microsoft: *"Use the `<summary>` tag to describe a type or a type member."* Do not pack rationale, design history, or parameter detail into it.14152. **The "why" goes in `<remarks>`.** Design intent, the role a type plays in the larger system, trade-offs, "this is the seam shared by X and Y" — all `<remarks>`. Microsoft: `<remarks>` *"add information ... supplementing the information specified with `<summary>`. This tag can include more lengthy explanations."*16173. **Per-parameter and return detail goes in `<param>` / `<returns>`, never folded into the summary.** The compiler validates `<param name="...">` against the actual signature and warns on mismatch, so you get a free correctness check. One `<param>` per parameter.18194. **Use the code-reference tags.** `<see cref="Type"/>` for cross-references (DocFX/Sandcastle turn these into links and the compiler verifies the target exists), `<paramref name="x"/>` to refer to a parameter inside prose, `<c>text</c>` for inline code/identifiers, `<code>` for multi-line blocks.20215. **Write complete sentences ending in a full stop.** Microsoft states this explicitly. Applies to every tag's text, including `<param>` and `<returns>`.22236. **Don't restate the signature or document the obvious.** A summary of "Gets or sets the name." on a `Name` property adds nothing. Document the non-obvious: units, nullability meaning, invariants, side effects, ordering guarantees.24257. **Use `<inheritdoc/>` instead of copy-pasting, and inherit in the right direction.** The base owns the canonical docs; the derived member inherits. The *interface* (or base type) carries the real `<summary>`/`<param>`/`<returns>`; the *implementation*, override, or sync/async twin gets a bare `<inheritdoc/>`. A bare `<inheritdoc/>` on an implementation auto-resolves to the interface member it implements, so no `cref` is needed. Putting the prose on the implementation and pointing the interface at it with `<inheritdoc cref="TheImplementation"/>` is backwards: the contract should document itself, and if the implementation is undocumented the interface inherits nothing. Reserve `<inheritdoc cref="..."/>` for links the compiler can't infer, such as a sync/async twin. Note the asymmetry: Visual Studio auto-inherits docs into IntelliSense even without the tag, but the **compiler-generated XML file does not** — so for any library you distribute, write `<inheritdoc/>` explicitly or consumers get empty docs.26278. **Generic and exception tags when they apply.** `<typeparam name="T">` for each type parameter; `<exception cref="...">` for exceptions a caller can reasonably expect and handle.2829## Tag cheat-sheet3031| Tag | Holds |32|-----|-------|33| `<summary>` | One sentence: what this type/member is. |34| `<remarks>` | Why it exists, design intent, longer explanation. |35| `<param name="x">` | What one parameter means. Compiler-validated. |36| `<returns>` | What the return value represents. |37| `<value>` | What a property's value represents. |38| `<typeparam name="T">` | What a generic type parameter is for. |39| `<typeparamref name="T"/>` | Refers to a generic type parameter inside prose (generic analog of `<paramref>`). |40| `<exception cref="E">` | An exception the member can throw. |41| `<see cref="M"/>` | Inline link to another code element. |42| `<paramref name="x"/>` | Refers to a parameter inside prose. |43| `<c>` / `<code>` | Inline / multi-line code formatting. |44| `<example>` | A usage example, usually wrapping a `<code>` block. |45| `<inheritdoc/>` | Inherit comments from base/interface/sync-twin. |4647## Examples4849### Good5051```csharp52/// <summary>53/// Resolves the target harness(es), runs their skill discovery, and returns the merged, ordered set.54/// </summary>55/// <remarks>56/// The reusable read-side seam shared by the <c>skills</c> listing command and skill sync, so neither57/// re-implements harness resolution or ordering. <see cref="ISkillCapability"/> is the data source;58/// this orchestrates over it and returns data only, never console markup.59/// </remarks>60internal sealed class SkillService { }6162/// <summary>Discovers skills across the selected harness(es), ordered for stable display.</summary>63/// <param name="harnessId">Limit the query to one harness; <see langword="null"/> queries every harness that supports discovery.</param>64/// <param name="harnessHome">Overrides the config root.</param>65/// <param name="projectStart">Where to begin the project-scope walk-up; <see langword="null"/> skips project scope.</param>66/// <returns>The discovered skills ordered by scope then name, or a typed failure.</returns>67public SkillDiscoveryResult Discover(string? harnessId, string? harnessHome, string? projectStart) { }68```6970### Bad7172```csharp73/// <summary>74/// The read side of "push a local skill": resolve which harnesses to ask, fan out their skill75/// discovery, and return the merged, ordered set. This is the reusable logic seam shared by the76/// <c>skills</c> listing command and (later) skill sync, so neither re-implements harness resolution77/// or ordering. The harness capability is the data source; this is the orchestration over it.78/// Returns data only, never console markup.79/// </summary>80// ^ Rationale crammed into <summary>. IntelliSense shows this whole wall at every call site.81// Split: one-line <summary>, the rest to <remarks>.8283/// <summary>84/// Discovers skills. harnessId limits to one harness (null = all); harnessHome overrides the config85/// root; projectStart is where to begin the project walk-up.86/// </summary>87// ^ Parameters described in prose instead of <param> tags — no compiler validation, harder to read.88```8990## Applying this9192When asked to tighten verbose doc comments: keep the existing wording, just **relocate** it. Move design rationale from `<summary>` to `<remarks>`, lift per-parameter prose into `<param>` tags, and trim the summary to its first declarative sentence. Information is preserved, not deleted.