Write or review C# XML documentation comments on public API surface. USE FOR: adding or reviewing XML documentation on public types, members, parameters, type parameters, and return values. DO NOT USE FOR: prose, markdown, README, or wiki text (use technical-writing), or ordinary code comments (use csharp-style).
Write reference documentation held to an enterprise production standard, not tutorial or learning material.
Document the contract a caller depends on, not the mechanics a reader can see in the signature.
The conventions match the .NET libraries.
When to use
Adding a public or protected type, member, or contract to a library or a shared assembly.
Reviewing XML documentation for coverage, tag order, opening phrasing, and contract accuracy.
Closing documentation gaps reported by CS1591 or CS1573.
Rules
Give every public and protected type, member, parameter, type parameter, and return value its tag.
Put the text on its own line between the opening and closing tag, indented four spaces past the /// marker; keep the one-line form for a single short clause.
Indent a nested tag one level further, and leave a <code> block at the indentation its rendered output needs.
Write one sentence per tag and end it with a period; a second sentence belongs in <remarks>.
Ignore the line length; the text of a tag stays on one line however long it runs, and no line is wrapped by hand.
State the contract; never restate the name or the parameter list in words.
Describe observable behavior, not the current implementation.
Describe the member as it stands, not the change that produced it; the caller reading the doc never saw the previous version.
State facts in the present indicative; never argue why.
Cut every purpose, result, cause, or comparison clause (so, that makes, which makes, because, rather than); if the clause states a fact the reader needs, make it its own sentence.
Tag order
Write the tags in this order.
<summary>
<value> — a property whose default or unit the caller needs
<typeparam> — one per type parameter, in declaration order
<param> — one per parameter, in declaration order
<returns>
<exception> — one per contract throw
<remarks>
<example>
Summary opening by member kind
The skeleton is identical for every declaration; the opening phrase is what changes.
Declaration
Summary opens with
Example
Class, struct, record
Represents …, Provides …, Defines …
Represents the host portion of a URI.
Static class holding extensions
Extension methods for …, Provides extension methods for …
Provides extension methods for <see cref="IEndpointRouteBuilder" /> to add endpoints.
Interface
Defines a contract that …, Provides an interface for …
Defines a contract that represents the result of an HTTP endpoint.
Attribute
Specifies …
Specifies a collection of tags in <see cref="Endpoint.Metadata" />.
Exception type
Represents … error
Represents an HTTP request error.
Delegate
A function that …, A delegate that …
A function that can process an HTTP request.
Enum
Determines …, Indicates whether …
Determines how cookie security properties are set.
Enum member
the effect of selecting the value
Opts out of compression over HTTPS.
Constant, static readonly field
the meaning of the value
HTTP status code 201.
Constructor
Initializes a new instance of the <see cref="T" /> class.
—
Read-only property
Gets …
Gets the HTTP status code for this exception.
Read-write property
Gets or sets …
Gets or sets the prefix used to identify the current object.
Boolean property
Gets a value indicating whether …
—
Event
An event that fires when …, An event that is raised when …
An event that is raised when a field value changes.
Factory method
Creates a <see cref="T" /> …
Creates a <see cref="ChallengeHttpResult" /> for the response.
Registration method
Adds … to the specified <see cref="IServiceCollection" />.
—
Override, interface implementation
<inheritdoc />
—
Explicit interface implementation
no doc comment
—
A delegate carries its <param> and <returns> on the type declaration.
An extension block carries a <param> for its receiver, placed on the block; each member inside the block documents only its own parameters.
Parameters, returns, and exceptions
Write <param> and <returns> as noun phrases opening with The, A, or An, and end them with a period.
An async method returns A task that represents the asynchronous <operation> operation.; name the produced value when the task carries one.
A builder or registration method returns The <see cref="T" /> for chaining. or A <see cref="T" /> that can be used to further customize the ….
A Try method documents the out parameter as When this method returns, contains …, and returns <see langword="true" /> if …; otherwise, <see langword="false" />..
State what a null or empty result means in <summary> or <returns>.
Reserve <exception> for a throw that is part of the contract; an argument guard at entry gets none.
Never leave a tag empty to silence CS1573; describe the parameter.
Inherited documentation
Put <inheritdoc /> on an override and on an implicit interface implementation.
Add <remarks> under <inheritdoc /> when the implementation adds a caller-visible constraint the base contract does not state.
Use <inheritdoc cref="…" /> when the source is not the immediate base member.
Leave an explicit interface implementation undocumented; the interface holds the documentation.
Cross-references and inline markup
Reference every type or member named in text with <see cref="…" />; renames stay linked.
Reference the current member's parameters with <paramref name="…" /> and <typeparamref name="…" />.
Use <c> for a literal value or fragment that names no symbol.
Link external documentation with <see href="https://…">Title</see>; HTTPS only.
Use <list type="bullet"> with <item><description>…</description></item> when the behavior branches on argument combinations.
Add a prose <example> on a parsing or accessor member to show what a concrete input yields.
Add <example> with <code> on an attribute or entry-point API whose call shape is not obvious from the signature; ordinary members get none.
Use <para> only to split a long <remarks>.
What belongs in remarks
A default value, a threading or reentrancy rule, an ownership or disposal rule, a platform limit, an interaction with another member, or the behavior on an edge input.
Examples
/// <summary>
/// Represents a session opened against a document store.
/// </summary>
public sealed class DocumentSession : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="DocumentSession" /> class.
/// </summary>
/// <param name="store">The store the session reads from.</param>
public DocumentSession(IDocumentStore store)
{
Store = store;
}
/// <summary>
/// Gets the store the session reads from.
/// </summary>
public IDocumentStore Store { get; }
/// <summary>
/// Gets or sets the timeout applied to every read.
/// </summary>
/// <value>Defaults to 30 seconds.</value>
public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
/// Gets a value indicating whether the session holds an open transaction.
/// </summary>
public bool IsTransactional { get; }
/// <summary>
/// An event that is raised when the session loads a document.
/// </summary>
public event EventHandler<DocumentLoadedEventArgs>? DocumentLoaded;
/// <summary>
/// Loads the document stored under the specified identifier.
/// </summary>
/// <param name="documentId">The identifier of the document to load.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> used to cancel the operation.</param>
/// <returns>
/// A task that represents the asynchronous load operation.
/// The task result contains the loaded document, or <see langword="null" /> when the identifier is unknown.
/// </returns>
/// <exception cref="ObjectDisposedException">The session is closed.</exception>
/// <remarks>The caller owns the returned document and disposes it.</remarks>
public async Task<Document?> LoadAsync(string documentId, CancellationToken cancellationToken = default)
{
...
}
/// <inheritdoc />
public void Dispose()
{
...
}
}
/// <summary>
/// Defines a contract that resolves a document by its identifier.
/// </summary>
public interface IDocumentResolver
{
/// <summary>
/// Resolves the document stored under the specified identifier.
/// </summary>
/// <param name="documentId">The identifier of the document to resolve.</param>
/// <param name="document">When this method returns, contains the resolved document if the identifier is known.</param>
/// <returns><see langword="true" /> if the document was resolved; otherwise, <see langword="false" />.</returns>
bool TryResolve(string documentId, [NotNullWhen(true)] out Document? document);
}
/// <summary>
/// Resolves a document from the local file system cache.
/// </summary>
public sealed class CachedDocumentResolver : IDocumentResolver
{
/// <inheritdoc />
public bool TryResolve(string documentId, [NotNullWhen(true)] out Document? document)
{
...
}
}
/// <summary>
/// Determines how a session resolves a document that is absent from the cache.
/// </summary>
public enum CacheMissBehavior
{
/// <summary>
/// Reads the document from the store and adds it to the cache.
/// </summary>
Fetch,
/// <summary>
/// Returns no document and leaves the cache unchanged.
/// </summary>
Skip
}
/// <summary>
/// A function that transforms a document before it reaches the caller.
/// </summary>
/// <param name="document">The document to transform.</param>
/// <returns>The transformed document.</returns>
public delegate Document DocumentTransform(Document document);
/// <summary>
/// Provides extension methods for <see cref="IServiceCollection" /> to register document storage.
/// </summary>
public static class DocumentStorageServiceCollectionExtensions
{
/// <param name="services">The <see cref="IServiceCollection" /> to add the services to.</param>
extension(IServiceCollection services)
{
/// <summary>
/// Adds the document storage services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="configure">An optional action to configure the <see cref="DocumentStorageOptions" />.</param>
/// <returns>The <see cref="IServiceCollection" /> for chaining.</returns>
public IServiceCollection AddDocumentStorage(Action<DocumentStorageOptions>? configure = null)
{
...
}
}
}
Validation
Every public and protected type, member, parameter, type parameter, and return value carries its tag; the build reports no CS1591 or CS1573.
Tags appear in the order summary, value, typeparam, param, returns, exception, remarks, example.
Multi-line tag text sits four spaces past the /// marker, and a nested tag sits one level further.
The summary opening matches the member kind, and a property opens with Gets, Gets or sets, or Gets a value indicating whether.
Overrides and implicit interface implementations use <inheritdoc />; explicit interface implementations carry no doc comment.
Every type and member named in text is a <see cref="…" />, and true, false, and null are <see langword="…" />.
<exception> lists only contract throws; argument guards at entry carry none.
Null and empty-result meaning appears in the summary or returns; ownership, defaults, and threading rules appear in remarks or value.
Text states facts about the member as it stands; none narrates the change or argues why.
Common Pitfalls
Pitfall
Correct approach
<summary>Gets the name.</summary> on GetName()
State what the name is and any constraint.
A property summary without Gets or Gets or sets
Open with the accessor verb the member exposes.
Re-summarizing an override or interface implementation
Use <inheritdoc />.
Documenting an explicit interface implementation
Leave it undocumented; the interface carries the contract.
An empty <param></param> added to silence CS1573
Describe the parameter.
<exception cref="ArgumentNullException"> for an entry guard
Document only a throw that is part of the contract.
<remarks> placed before <param> on a method
Put <remarks> after <returns>.
Multi-line tag text left flush against ///
Indent it four spaces past the marker.
Documenting the implementation ("loops over items")
Describe the observable contract.
Hardcoding a type name in prose
Use <see cref="TypeName" />.
Writing true, false, or null as plain text
Use <see langword="true" />.
Ownership, default value, or unit left implicit
State it in <remarks> or <value>.
Narrating the change ("now returns null when…")
Describe the member as it stands.
1---2name: writing-xml-doc-comments3description: Write or review C# XML documentation comments on public API surface. USE FOR: adding or reviewing XML documentation on public types, members, parameters, type parameters, and return values. DO NOT USE FOR: prose, markdown, README, or wiki text (use technical-writing), or ordinary code comments (use csharp-style).4license: MIT5---67# Writing XML Doc Comments89Write reference documentation held to an enterprise production standard, not tutorial or learning material.10Document the contract a caller depends on, not the mechanics a reader can see in the signature.11The conventions match the .NET libraries.1213## When to use1415- Adding a public or protected type, member, or contract to a library or a shared assembly.16- Reviewing XML documentation for coverage, tag order, opening phrasing, and contract accuracy.17- Closing documentation gaps reported by CS1591 or CS1573.1819## Rules2021- Give every public and protected type, member, parameter, type parameter, and return value its tag.22- Put the text on its own line between the opening and closing tag, indented four spaces past the `///` marker; keep the one-line form for a single short clause.23- Indent a nested tag one level further, and leave a `<code>` block at the indentation its rendered output needs.24- Write one sentence per tag and end it with a period; a second sentence belongs in `<remarks>`.25- Ignore the line length; the text of a tag stays on one line however long it runs, and no line is wrapped by hand.26- State the contract; never restate the name or the parameter list in words.27- Describe observable behavior, not the current implementation.28- Describe the member as it stands, not the change that produced it; the caller reading the doc never saw the previous version.29- State facts in the present indicative; never argue why.30- Cut every purpose, result, cause, or comparison clause (`so`, `that makes`, `which makes`, `because`, `rather than`); if the clause states a fact the reader needs, make it its own sentence.3132## Tag order3334Write the tags in this order.35361. `<summary>`372. `<value>` — a property whose default or unit the caller needs383. `<typeparam>` — one per type parameter, in declaration order394. `<param>` — one per parameter, in declaration order405. `<returns>`416. `<exception>` — one per contract throw427. `<remarks>`438. `<example>`4445## Summary opening by member kind4647The skeleton is identical for every declaration; the opening phrase is what changes.4849| Declaration | Summary opens with | Example |50|------------------------------------|----------------------------------------------------------------|-----------------------------------------------------------------------------------------|51| Class, struct, record | `Represents …`, `Provides …`, `Defines …` | `Represents the host portion of a URI.` |52| Static class holding extensions | `Extension methods for …`, `Provides extension methods for …` | `Provides extension methods for <see cref="IEndpointRouteBuilder" /> to add endpoints.` |53| Interface | `Defines a contract that …`, `Provides an interface for …` | `Defines a contract that represents the result of an HTTP endpoint.` |54| Attribute | `Specifies …` | `Specifies a collection of tags in <see cref="Endpoint.Metadata" />.` |55| Exception type | `Represents … error` | `Represents an HTTP request error.` |56| Delegate | `A function that …`, `A delegate that …` | `A function that can process an HTTP request.` |57| Enum | `Determines …`, `Indicates whether …` | `Determines how cookie security properties are set.` |58| Enum member | the effect of selecting the value | `Opts out of compression over HTTPS.` |59| Constant, static readonly field | the meaning of the value | `HTTP status code 201.` |60| Constructor | `Initializes a new instance of the <see cref="T" /> class.` | — |61| Read-only property | `Gets …` | `Gets the HTTP status code for this exception.` |62| Read-write property | `Gets or sets …` | `Gets or sets the prefix used to identify the current object.` |63| Boolean property | `Gets a value indicating whether …` | — |64| Event | `An event that fires when …`, `An event that is raised when …` | `An event that is raised when a field value changes.` |65| Factory method | `Creates a <see cref="T" /> …` | `Creates a <see cref="ChallengeHttpResult" /> for the response.` |66| Registration method | `Adds … to the specified <see cref="IServiceCollection" />.` | — |67| Override, interface implementation | `<inheritdoc />` | — |68| Explicit interface implementation | no doc comment | — |6970A delegate carries its `<param>` and `<returns>` on the type declaration.71An `extension` block carries a `<param>` for its receiver, placed on the block; each member inside the block documents only its own parameters.7273## Parameters, returns, and exceptions7475- Write `<param>` and `<returns>` as noun phrases opening with `The`, `A`, or `An`, and end them with a period.76- An async method returns `A task that represents the asynchronous <operation> operation.`; name the produced value when the task carries one.77- A builder or registration method returns `The <see cref="T" /> for chaining.` or `A <see cref="T" /> that can be used to further customize the …`.78- A `Try` method documents the `out` parameter as `When this method returns, contains …`, and returns `<see langword="true" /> if …; otherwise, <see langword="false" />.`.79- State what a null or empty result means in `<summary>` or `<returns>`.80- Reserve `<exception>` for a throw that is part of the contract; an argument guard at entry gets none.81- Never leave a tag empty to silence CS1573; describe the parameter.8283## Inherited documentation8485- Put `<inheritdoc />` on an override and on an implicit interface implementation.86- Add `<remarks>` under `<inheritdoc />` when the implementation adds a caller-visible constraint the base contract does not state.87- Use `<inheritdoc cref="…" />` when the source is not the immediate base member.88- Leave an explicit interface implementation undocumented; the interface holds the documentation.8990## Cross-references and inline markup9192- Reference every type or member named in text with `<see cref="…" />`; renames stay linked.93- Reference the current member's parameters with `<paramref name="…" />` and `<typeparamref name="…" />`.94- Write keywords as `<see langword="true" />`, `<see langword="false" />`, `<see langword="null" />`.95- Use `<c>` for a literal value or fragment that names no symbol.96- Link external documentation with `<see href="https://…">Title</see>`; HTTPS only.97- Use `<list type="bullet">` with `<item><description>…</description></item>` when the behavior branches on argument combinations.98- Add a prose `<example>` on a parsing or accessor member to show what a concrete input yields.99- Add `<example>` with `<code>` on an attribute or entry-point API whose call shape is not obvious from the signature; ordinary members get none.100- Use `<para>` only to split a long `<remarks>`.101102## What belongs in remarks103104A default value, a threading or reentrancy rule, an ownership or disposal rule, a platform limit, an interaction with another member, or the behavior on an edge input.105106## Examples107108```csharp109/// <summary>110/// Represents a session opened against a document store.111/// </summary>112public sealed class DocumentSession : IDisposable113{114 /// <summary>115 /// Initializes a new instance of the <see cref="DocumentSession" /> class.116 /// </summary>117 /// <param name="store">The store the session reads from.</param>118 public DocumentSession(IDocumentStore store)119 {120 Store = store;121 }122123 /// <summary>124 /// Gets the store the session reads from.125 /// </summary>126 public IDocumentStore Store { get; }127128 /// <summary>129 /// Gets or sets the timeout applied to every read.130 /// </summary>131 /// <value>Defaults to 30 seconds.</value>132 public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromSeconds(30);133134 /// <summary>135 /// Gets a value indicating whether the session holds an open transaction.136 /// </summary>137 public bool IsTransactional { get; }138139 /// <summary>140 /// An event that is raised when the session loads a document.141 /// </summary>142 public event EventHandler<DocumentLoadedEventArgs>? DocumentLoaded;143144 /// <summary>145 /// Loads the document stored under the specified identifier.146 /// </summary>147 /// <param name="documentId">The identifier of the document to load.</param>148 /// <param name="cancellationToken">A <see cref="CancellationToken" /> used to cancel the operation.</param>149 /// <returns>150 /// A task that represents the asynchronous load operation.151 /// The task result contains the loaded document, or <see langword="null" /> when the identifier is unknown.152 /// </returns>153 /// <exception cref="ObjectDisposedException">The session is closed.</exception>154 /// <remarks>The caller owns the returned document and disposes it.</remarks>155 public async Task<Document?> LoadAsync(string documentId, CancellationToken cancellationToken = default)156 {157 ...158 }159160 /// <inheritdoc />161 public void Dispose()162 {163 ...164 }165}166```167168```csharp169/// <summary>170/// Defines a contract that resolves a document by its identifier.171/// </summary>172public interface IDocumentResolver173{174 /// <summary>175 /// Resolves the document stored under the specified identifier.176 /// </summary>177 /// <param name="documentId">The identifier of the document to resolve.</param>178 /// <param name="document">When this method returns, contains the resolved document if the identifier is known.</param>179 /// <returns><see langword="true" /> if the document was resolved; otherwise, <see langword="false" />.</returns>180 bool TryResolve(string documentId, [NotNullWhen(true)] out Document? document);181}182183/// <summary>184/// Resolves a document from the local file system cache.185/// </summary>186public sealed class CachedDocumentResolver : IDocumentResolver187{188 /// <inheritdoc />189 public bool TryResolve(string documentId, [NotNullWhen(true)] out Document? document)190 {191 ...192 }193}194```195196```csharp197/// <summary>198/// Determines how a session resolves a document that is absent from the cache.199/// </summary>200public enum CacheMissBehavior201{202 /// <summary>203 /// Reads the document from the store and adds it to the cache.204 /// </summary>205 Fetch,206207 /// <summary>208 /// Returns no document and leaves the cache unchanged.209 /// </summary>210 Skip211}212213/// <summary>214/// A function that transforms a document before it reaches the caller.215/// </summary>216/// <param name="document">The document to transform.</param>217/// <returns>The transformed document.</returns>218public delegate Document DocumentTransform(Document document);219```220221```csharp222/// <summary>223/// Provides extension methods for <see cref="IServiceCollection" /> to register document storage.224/// </summary>225public static class DocumentStorageServiceCollectionExtensions226{227 /// <param name="services">The <see cref="IServiceCollection" /> to add the services to.</param>228 extension(IServiceCollection services)229 {230 /// <summary>231 /// Adds the document storage services to the specified <see cref="IServiceCollection" />.232 /// </summary>233 /// <param name="configure">An optional action to configure the <see cref="DocumentStorageOptions" />.</param>234 /// <returns>The <see cref="IServiceCollection" /> for chaining.</returns>235 public IServiceCollection AddDocumentStorage(Action<DocumentStorageOptions>? configure = null)236 {237 ...238 }239 }240}241```242243## Validation244245- [ ] Every public and protected type, member, parameter, type parameter, and return value carries its tag; the build reports no CS1591 or CS1573.246- [ ] Tags appear in the order summary, value, typeparam, param, returns, exception, remarks, example.247- [ ] Multi-line tag text sits four spaces past the `///` marker, and a nested tag sits one level further.248- [ ] The summary opening matches the member kind, and a property opens with `Gets`, `Gets or sets`, or `Gets a value indicating whether`.249- [ ] Overrides and implicit interface implementations use `<inheritdoc />`; explicit interface implementations carry no doc comment.250- [ ] Every type and member named in text is a `<see cref="…" />`, and `true`, `false`, and `null` are `<see langword="…" />`.251- [ ] `<exception>` lists only contract throws; argument guards at entry carry none.252- [ ] Null and empty-result meaning appears in the summary or returns; ownership, defaults, and threading rules appear in remarks or value.253- [ ] Text states facts about the member as it stands; none narrates the change or argues why.254255## Common Pitfalls256257| Pitfall | Correct approach |258|---------------------------------------------------------------|------------------------------------------------------------|259| `<summary>Gets the name.</summary>` on `GetName()` | State what the name is and any constraint. |260| A property summary without `Gets` or `Gets or sets` | Open with the accessor verb the member exposes. |261| Re-summarizing an override or interface implementation | Use `<inheritdoc />`. |262| Documenting an explicit interface implementation | Leave it undocumented; the interface carries the contract. |263| An empty `<param></param>` added to silence CS1573 | Describe the parameter. |264| `<exception cref="ArgumentNullException">` for an entry guard | Document only a throw that is part of the contract. |265| `<remarks>` placed before `<param>` on a method | Put `<remarks>` after `<returns>`. |266| Multi-line tag text left flush against `///` | Indent it four spaces past the marker. |267| Documenting the implementation ("loops over items") | Describe the observable contract. |268| Hardcoding a type name in prose | Use `<see cref="TypeName" />`. |269| Writing `true`, `false`, or `null` as plain text | Use `<see langword="true" />`. |270| Ownership, default value, or unit left implicit | State it in `<remarks>` or `<value>`. |271| Narrating the change ("now returns null when…") | Describe the member as it stands. |
Run npx skillmds@latest add nice3point/writing-xml-doc-comments in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Write or review C# XML documentation comments on public API surface. USE FOR: adding or reviewing XML documentation on public types, members, parameters, type parameters, and return values. DO NOT USE FOR: prose, markdown, README, or wiki text (use technical-writing), or ordinary code comments (use csharp-style). It is listed under Docs & Writing on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
nice3point (@nice3point) published this skill. Their other Agent Skills are listed on their SkillMD profile.