C#ドキュメントコメント完全ガイド|XMLタグの書き方からIntelliSense表示・保守しやすいコード例まで
はじめに
C#でクラスやメソッドを書いていると、/// <summary> のようなコメントを見かけることがあります。これは単なるメモではなく、Visual StudioやVisual Studio CodeのIntelliSenseに説明を表示したり、XMLドキュメントファイルとして出力したり、APIリファレンスを自動生成したりするための「ドキュメントコメント」です。
特にライブラリ開発、チーム開発、長期運用される業務システムでは、C#ドキュメントコメントの品質がコードの使いやすさに直結します。メソッド名だけでは伝わらない前提条件、引数の意味、戻り値の扱い、発生し得る例外などを適切に書いておくことで、利用者は実装を読まずに安全にAPIを使えるようになります。
一方で、すべての処理に長い説明を書けばよいわけではありません。コードと同じ内容を繰り返すだけのコメントは、保守コストを増やし、実装変更時に古い情報として残ってしまうことがあります。
この記事では、C#ドキュメントコメントの基本構文、よく使うXMLタグ、IntelliSenseへの表示、XMLドキュメントファイルの出力方法、実務で使えるコード例、保守しやすい書き方までを体系的に解説します。
1. C#ドキュメントコメントとは?検索ユーザーが最初に知りたい全体像
C#ドキュメントコメントとは、クラス、メソッド、プロパティ、イベント、フィールドなどのメンバーに対して、XML形式で説明を書くためのコメントです。C#では主に /// で始まるコメントとして記述します。
代表的な例は次のような形です。
C#/// <summary>
/// 指定された税込価格から税抜価格を計算します。
/// </summary>
/// <param name="taxIncludedPrice">税込価格。</param>
/// <returns>税抜価格。</returns>
public decimal CalculateTaxExcludedPrice(decimal taxIncludedPrice)
{
return taxIncludedPrice / 1.1m;
}
このコメントは、単にソースコード上で読むためだけのものではありません。IDEの入力補完で説明として表示されたり、ビルド時にXMLファイルとして出力されたり、DocFXなどのツールを使ってHTML形式のAPIドキュメントに変換されたりします。
つまりC#ドキュメントコメントは、コードの利用者に向けた「使い方の説明書」として機能します。
1-1. C#の「ドキュメント」と「コメント」の違い
C#における「コメント」は、ソースコード中に書く補足説明全般を指します。たとえば // や /* */ を使って、処理の意図や注意点を書きます。
C#// 小数点以下を切り捨てる
var amount = Math.Floor(price);
一方で「ドキュメントコメント」は、クラスやメソッドなどの公開APIに対して、外部から参照できる説明を付けるためのコメントです。XMLタグを使って構造化して書く点が特徴です。
C#/// <summary>
/// 小数点以下を切り捨てた金額を取得します。
/// </summary>
public decimal RoundedAmount { get; }
通常のコメントは主に「実装を読む人」に向けた説明です。ドキュメントコメントは主に「そのクラスやメソッドを使う人」に向けた説明です。この違いを理解すると、何を書くべきかが明確になります。
1-2. /// で書くXMLドキュメントコメントの役割
C#では、メンバーの直前に /// を書くとXMLドキュメントコメントとして扱われます。Visual Studioでは、クラスやメソッドの上で /// を入力すると、summary や param などのテンプレートが自動生成されます。
C#/// <summary>
/// 商品コードを表します。
/// </summary>
public string ProductCode { get; set; }
/// コメントの主な役割は次のとおりです。
・クラスやメソッドの概要を説明する
・引数や戻り値の意味を明確にする
・発生する可能性のある例外を伝える
・IntelliSenseに説明を表示する
・XMLドキュメントファイルとして出力する
・API仕様書やリファレンス生成に利用する
特に、publicなクラスやメソッドを他の開発者が利用する場合、ドキュメントコメントがあるかどうかで開発体験は大きく変わります。
1-3. 通常のコメントとの違い:IntelliSense・XML出力・API仕様書化
通常の // コメントは、ソースコードを開いた人にしか見えません。NuGetパッケージやDLLとして配布された場合、利用者がそのコメントを読む機会はほとんどありません。
一方、XMLドキュメントコメントは、ビルド時にXMLファイルとして出力できます。そのXMLファイルをDLLと一緒に配布すると、利用側のIDEでIntelliSenseとして説明が表示されます。
たとえば、次のようなメソッドがあるとします。
C#/// <summary>
/// 指定されたユーザーIDに対応するユーザー情報を取得します。
/// </summary>
/// <param name="userId">取得対象のユーザーID。</param>
/// <returns>ユーザー情報。存在しない場合は null。</returns>
public User? FindUser(int userId)
{
// 実装
}
このコメントは、メソッド呼び出し時に概要、引数、戻り値の説明として表示されます。これにより、利用者は実装を読まずにメソッドの使い方を理解できます。
さらに、DocFXなどのドキュメント生成ツールと組み合わせれば、XMLコメントからAPIリファレンスを自動生成できます。通常のコメントと比べて、ドキュメントコメントは再利用性が高く、開発支援ツールとの相性がよい点が大きな違いです。
1-4. どんな人がC#ドキュメントコメントを書くべきか
C#ドキュメントコメントは、次のような人に特に必要です。
・クラスライブラリを開発している人
・NuGetパッケージを公開している人
・社内共通ライブラリを保守している人
・APIやSDKを提供している人
・複数人でC#プロジェクトを開発している人
・将来の自分が読み返すコードを書いている人
小規模な個人プロジェクトでも、複雑な仕様や間違いやすい使い方があるメソッドにはドキュメントコメントを書いておく価値があります。
ただし、すべてのprivateメソッドに機械的にコメントを書く必要はありません。外部から利用されるpublic API、意味が分かりにくい引数、例外条件がある処理、業務ルールを含む処理など、利用者が迷いやすい部分を優先すると効果的です。
1-5. この記事でできるようになること
この記事を読むことで、次のことができるようになります。
・C#ドキュメントコメントの基本構文を理解できる
・summary、param、returnsなどのXMLタグを使い分けられる
・IntelliSenseに分かりやすい説明を表示できる
・XMLドキュメントファイルを出力できる
・クラス、メソッド、プロパティ、非同期処理に適切なコメントを書ける
・保守しやすく、古くなりにくいコメントを書ける
・よくあるエラーや警告に対処できる
C#ドキュメントコメントは、難しい文法を覚えるよりも「誰が、何を知りたいか」を意識することが重要です。以降では、実際のコード例を使いながら順番に解説します。
2. C#ドキュメントコメントの基本構文
C#ドキュメントコメントの基本は、対象となるクラスやメソッドの直前に /// を書き、XMLタグで説明を囲むことです。
最もよく使うタグは <summary> です。これは、対象メンバーの概要を表します。
C#/// <summary>
/// 商品の在庫数を取得します。
/// </summary>
public int StockQuantity { get; private set; }
ドキュメントコメントはXMLとして扱われるため、タグの閉じ忘れや特殊文字の扱いには注意が必要です。構文が正しくないと、ビルド時に警告が出たり、XMLドキュメントファイルが期待どおりに生成されなかったりします。
2-1. ///コメントの書き方
C#では、1行コメントとして //、複数行コメントとして /* */、ドキュメントコメントとして /// を使います。
C#// 通常のコメント
var price = 1000;
/*
複数行の通常コメント
*/
var taxRate = 0.1m;
/// <summary>
/// 税率を取得します。
/// </summary>
public decimal TaxRate { get; }
ドキュメントコメントは、対象のメンバーの直前に書きます。間に別のコードや無関係な記述を挟まないようにします。
C#/// <summary>
/// 注文を確定します。
/// </summary>
public void Confirm()
{
// 実装
}
メソッドに引数がある場合は <param>、戻り値がある場合は <returns> を追加します。
C#/// <summary>
/// 指定された数量で合計金額を計算します。
/// </summary>
/// <param name="quantity">購入数量。</param>
/// <returns>合計金額。</returns>
public decimal CalculateTotal(int quantity)
{
return UnitPrice * quantity;
}
2-2. クラス・メソッド・プロパティに書く基本形
クラスには、そのクラスが何を表すのかを書きます。
C#/// <summary>
/// 注文情報を表します。
/// </summary>
public class Order
{
}
メソッドには、何を行うメソッドなのか、必要に応じて引数や戻り値を書きます。
C#/// <summary>
/// 注文に商品を追加します。
/// </summary>
/// <param name="product">追加する商品。</param>
/// <param name="quantity">追加する数量。</param>
public void AddItem(Product product, int quantity)
{
// 実装
}
プロパティには、その値が何を意味するのかを書きます。
C#/// <summary>
/// 注文の合計金額を取得します。
/// </summary>
public decimal TotalAmount { get; private set; }
プロパティの場合は <summary> だけでも十分なことが多いですが、値の範囲や単位、nullになる条件などを説明したい場合は <value> を使うこともできます。
C#/// <summary>
/// 割引率を取得または設定します。
/// </summary>
/// <value>0以上1以下の値。0.1は10%割引を表します。</value>
public decimal DiscountRate { get; set; }
2-3. Visual Studioでコメントテンプレートを自動生成する方法
Visual Studioでは、クラスやメソッドの直前で /// と入力すると、ドキュメントコメントのテンプレートが自動生成されます。
たとえば、次のようなメソッドの直前で /// を入力します。
C#public decimal CalculateTotal(decimal price, int quantity)
{
return price * quantity;
}
すると、次のようなコメントが生成されます。
C#/// <summary>
///
/// </summary>
/// <param name="price"></param>
/// <param name="quantity"></param>
/// <returns></returns>
public decimal CalculateTotal(decimal price, int quantity)
{
return price * quantity;
}
あとは空欄を埋めるだけです。
C#/// <summary>
/// 単価と数量から合計金額を計算します。
/// </summary>
/// <param name="price">商品の単価。</param>
/// <param name="quantity">購入数量。</param>
/// <returns>合計金額。</returns>
public decimal CalculateTotal(decimal price, int quantity)
{
return price * quantity;
}
テンプレートを使うと、param の書き忘れや引数名の誤りを減らせます。ただし、自動生成された空のコメントをそのまま残すと、逆に読みづらくなります。必ず利用者にとって意味のある説明に書き換えましょう。
2-4. XMLとして正しく書くための注意点
C#ドキュメントコメントはXMLとして解釈されるため、通常の文章を書くときとは少し注意点があります。
特に重要なのは、特殊文字のエスケープです。
< は <
> は >
& は &
たとえば、次のように書くとXMLとして不正になることがあります。
C#/// <summary>
/// count < 0 の場合はエラーになります。
/// </summary>
正しくは次のように書きます。
C#/// <summary>
/// count < 0 の場合はエラーになります。
/// </summary>
また、タグは必ず閉じる必要があります。
C#/// <summary>
/// 注文を確定します。
/// </summary>
次のように閉じタグがないコメントは避けます。
C#/// <summary>
/// 注文を確定します。
public void Confirm()
{
}
XMLタグを説明文として表示したい場合も、エスケープや <c>、<code> を使って読みやすくします。
C#/// <summary>
/// <c>null</c> の場合は既定値を使用します。
/// </summary>
2-5. まず覚えるべき最小構成のサンプルコード
最初に覚えるべきタグは、summary、param、returns の3つです。この3つだけでも、メソッドの使い方はかなり分かりやすくなります。
C#/// <summary>
/// 指定された商品の税込価格を計算します。
/// </summary>
/// <param name="price">税抜価格。</param>
/// <param name="taxRate">税率。10%の場合は0.1を指定します。</param>
/// <returns>税込価格。</returns>
public decimal CalculateTaxIncludedPrice(decimal price, decimal taxRate)
{
return price * (1 + taxRate);
}
このコメントでは、メソッドの目的、引数の意味、戻り値の内容が明確です。利用者は、taxRate に 10 を渡すべきなのか 0.1 を渡すべきなのかを迷わず判断できます。
C#ドキュメントコメントでは、このように「名前だけでは誤解されそうな情報」を補うことが重要です。
3. よく使うXMLタグ一覧と使い分け
C#ドキュメントコメントでは、用途に応じて複数のXMLタグを使い分けます。すべてを最初から覚える必要はありませんが、よく使うタグの意味を知っておくと、読みやすく実用的なコメントを書けるようになります。
基本的な考え方は次のとおりです。
summary:概要
param:引数
returns:戻り値
remarks:補足説明
exception:例外
value:プロパティ値
example:使用例
see / seealso:関連参照
typeparam:ジェネリック型パラメータ
code / c:コード表示
list:箇条書き
inheritdoc:説明の継承
以降では、それぞれのタグの使い方を具体例とともに解説します。
3-1. summary:クラスやメソッドの概要を書く
summary は、C#ドキュメントコメントで最も基本となるタグです。クラス、メソッド、プロパティなどの概要を書きます。
C#/// <summary>
/// 注文の支払い状態を管理します。
/// </summary>
public class PaymentStatusManager
{
}
メソッドの場合は、何をするメソッドなのかを簡潔に書きます。
C#/// <summary>
/// 指定された注文をキャンセルします。
/// </summary>
public void CancelOrder(int orderId)
{
}
よい summary は、短く、具体的で、利用者目線です。
悪い例です。
C#/// <summary>
/// キャンセルします。
/// </summary>
public void CancelOrder(int orderId)
{
}
何をキャンセルするのかが分かりません。
よい例です。
C#/// <summary>
/// 指定された注文をキャンセルし、在庫を戻します。
/// </summary>
public void CancelOrder(int orderId)
{
}
このように、メソッド名だけでは分からない重要な挙動を含めると実用的です。
3-2. param:引数の意味を書く
param は、メソッドやコンストラクターの引数を説明するタグです。name 属性には、実際の引数名を正確に指定します。
C#/// <summary>
/// 商品をカートに追加します。
/// </summary>
/// <param name="productId">追加する商品のID。</param>
/// <param name="quantity">追加する数量。</param>
public void AddToCart(int productId, int quantity)
{
}
param では、型名を繰り返すだけでは不十分です。
悪い例です。
C#/// <param name="quantity">int型の数量。</param>
quantity が数量であることは名前と型から分かります。重要なのは、許可される範囲や単位、業務上の意味です。
よい例です。
C#/// <param name="quantity">追加する数量。1以上を指定します。</param>
引数に null を許容するか、空文字を許容するか、IDが存在しない場合どうなるかなども、必要に応じて書くと親切です。
C#/// <param name="email">検索対象のメールアドレス。大文字と小文字は区別されません。</param>
3-3. returns:戻り値を説明する
returns は、戻り値の意味を説明するタグです。戻り値があるメソッドに使います。
C#/// <summary>
/// 指定されたユーザーIDに対応するユーザーを取得します。
/// </summary>
/// <param name="userId">ユーザーID。</param>
/// <returns>ユーザーが存在する場合はユーザー情報。それ以外の場合は null。</returns>
public User? FindUser(int userId)
{
return null;
}
戻り値の型が bool の場合は、true と false の意味を明確に書きます。
C#/// <summary>
/// 指定された商品が購入可能かどうかを判定します。
/// </summary>
/// <param name="productId">商品ID。</param>
/// <returns>購入可能な場合は true。それ以外の場合は false。</returns>
public bool CanPurchase(int productId)
{
return true;
}
戻り値がコレクションの場合は、空のコレクションを返すのか、null を返す可能性があるのかを書くと、利用者が安全に扱えます。
C#/// <returns>条件に一致する注文一覧。該当する注文がない場合は空のコレクション。</returns>
3-4. remarks:補足説明や詳細仕様を書く
remarks は、summary だけでは説明しきれない詳細や補足情報を書くためのタグです。
C#/// <summary>
/// 注文を確定します。
/// </summary>
/// <remarks>
/// このメソッドは在庫の引き当てと支払い状態の更新を同一トランザクション内で実行します。
/// 在庫不足の場合、注文は確定されません。
/// </remarks>
public void ConfirmOrder()
{
}
summary は短く、remarks は詳しく、という役割分担にすると読みやすくなります。
remarks に向いている内容は次のようなものです。
・業務ルール
・副作用
・トランザクションの扱い
・パフォーマンス上の注意
・スレッドセーフかどうか
・利用時の前提条件
ただし、実装の細かい手順をすべて書く必要はありません。実装が変わるたびにコメントも修正しなければならなくなるため、外部から見た仕様や注意点を中心に書きます。
3-5. exception:発生する例外を書く
exception は、メソッドがスローする可能性のある例外を説明するタグです。cref 属性には例外型を指定します。
C#/// <summary>
/// 指定された数量を在庫から引き当てます。
/// </summary>
/// <param name="quantity">引き当てる数量。</param>
/// <exception cref="ArgumentOutOfRangeException">
/// quantity が1未満の場合にスローされます。
/// </exception>
/// <exception cref="InvalidOperationException">
/// 在庫が不足している場合にスローされます。
/// </exception>
public void ReserveStock(int quantity)
{
if (quantity < 1)
{
throw new ArgumentOutOfRangeException(nameof(quantity));
}
if (StockQuantity < quantity)
{
throw new InvalidOperationException("在庫が不足しています。");
}
}
exception には、例外名だけでなく、どの条件で発生するのかを書きます。利用者にとって重要なのは「どんな例外が出るか」だけではなく、「どういう入力や状態で出るか」です。
3-6. value:プロパティの値を説明する
value は、プロパティの値の意味を説明するタグです。summary だけで十分な場合も多いですが、値の範囲、単位、既定値、null許容などを説明したい場合に便利です。
C#/// <summary>
/// 消費税率を取得または設定します。
/// </summary>
/// <value>0以上1以下の値。10%の場合は0.1を指定します。</value>
public decimal TaxRate { get; set; }
次のように、プロパティの意味だけでなく値の制約を書くと実務で役立ちます。
C#/// <summary>
/// 1ページあたりの表示件数を取得または設定します。
/// </summary>
/// <value>1以上100以下の値。</value>
public int PageSize { get; set; } = 20;
プロパティ名から意味が明らかな場合でも、単位や範囲が重要な場合は value を使う価値があります。
3-7. example:使用例を書く
example は、使い方の例を書くためのタグです。複雑なメソッドや、引数の指定方法に注意が必要なAPIに向いています。
C#/// <summary>
/// 税込価格を計算します。
/// </summary>
/// <param name="price">税抜価格。</param>
/// <param name="taxRate">税率。10%の場合は0.1を指定します。</param>
/// <returns>税込価格。</returns>
/// <example>
/// <code>
/// var calculator = new TaxCalculator();
/// var result = calculator.CalculateTaxIncludedPrice(1000m, 0.1m);
/// // result は 1100
/// </code>
/// </example>
public decimal CalculateTaxIncludedPrice(decimal price, decimal taxRate)
{
return price * (1 + taxRate);
}
example は、APIドキュメントを生成したときに特に有効です。IntelliSenseではすべての内容が見やすく表示されるとは限らないため、長い使用例はAPIリファレンス向けと考えるとよいでしょう。
3-8. see・seealso:関連メンバーや外部参照を書く
see と seealso は、関連する型やメソッドへの参照を示すタグです。
see は文章中で関連メンバーを参照したいときに使います。
C#/// <summary>
/// 注文を確定します。キャンセルする場合は <see cref="Cancel"/> を使用します。
/// </summary>
public void Confirm()
{
}
seealso は関連項目として別メンバーを示すときに使います。
C#/// <summary>
/// 注文をキャンセルします。
/// </summary>
/// <seealso cref="Confirm"/>
public void Cancel()
{
}
外部URLを参照したい場合は、href を使うこともできます。
C#/// <summary>
/// 郵便番号を検証します。
/// </summary>
/// <seealso href="https://example.com/postal-code-rules">郵便番号仕様</seealso>
public bool ValidatePostalCode(string postalCode)
{
return true;
}
ただし、外部リンクはURL変更で切れる可能性があります。社内仕様書や公開ドキュメントを参照する場合は、運用ルールを決めておくと安心です。
3-9. typeparam:ジェネリック型パラメータを書く
typeparam は、ジェネリック型やジェネリックメソッドの型パラメータを説明するタグです。
C#/// <summary>
/// 指定されたIDに対応するエンティティを取得します。
/// </summary>
/// <typeparam name="TEntity">取得するエンティティの型。</typeparam>
/// <param name="id">エンティティのID。</param>
/// <returns>取得したエンティティ。存在しない場合は null。</returns>
public TEntity? Find<TEntity>(int id) where TEntity : class
{
return null;
}
型パラメータ名が T や TResult のように抽象的な場合、typeparam の説明があると利用者が理解しやすくなります。
C#/// <typeparam name="TResult">変換後の結果の型。</typeparam>
ジェネリック制約がある場合は、制約の意図を remarks に書くこともあります。
C#/// <remarks>
/// <typeparamref name="TEntity"/> は、引数なしコンストラクターを持つエンティティ型である必要があります。
/// </remarks>
3-10. code・c:コードや識別子を見やすく表示する
c は短いコード片や識別子をインラインで表示するためのタグです。
C#/// <summary>
/// <c>null</c> または空文字の場合は既定値を使用します。
/// </summary>
public string NormalizeName(string? name)
{
return string.IsNullOrWhiteSpace(name) ? "Unknown" : name;
}
code は複数行のコード例に使います。
C#/// <example>
/// <code>
/// var service = new OrderService();
/// service.Confirm(orderId);
/// </code>
/// </example>
public void Confirm(int orderId)
{
}
識別子を文章中にそのまま書くより、c や code を使うと、ドキュメント生成時に読みやすくなります。
C#/// <summary>
/// <c>OrderStatus.Pending</c> の注文のみ確定できます。
/// </summary>
3-11. list:箇条書きで説明する
list は、箇条書きや表形式で説明したい場合に使います。
C#/// <summary>
/// 注文状態を更新します。
/// </summary>
/// <remarks>
/// 更新できる状態は次のとおりです。
/// <list type="bullet">
/// <item><description>Pending: 未確定</description></item>
/// <item><description>Confirmed: 確定済み</description></item>
/// <item><description>Canceled: キャンセル済み</description></item>
/// </list>
/// </remarks>
public void UpdateStatus(OrderStatus status)
{
}
複数の条件や手順を説明する場合、長い文章で書くよりも list を使った方が読みやすくなります。
番号付きリストにしたい場合は、type="number" を使います。
C#/// <list type="number">
/// <item><description>入力値を検証します。</description></item>
/// <item><description>在庫を確認します。</description></item>
/// <item><description>注文を確定します。</description></item>
/// </list>
3-12. inheritdoc:基底クラスやインターフェイスの説明を継承する
inheritdoc は、基底クラスやインターフェイスのドキュメントコメントを継承するためのタグです。
C#public interface IRepository<T>
{
/// <summary>
/// 指定されたIDに対応するエンティティを取得します。
/// </summary>
/// <param name="id">エンティティのID。</param>
/// <returns>エンティティ。存在しない場合は null。</returns>
T? Find(int id);
}
public class UserRepository : IRepository<User>
{
/// <inheritdoc />
public User? Find(int id)
{
return null;
}
}
インターフェイスで仕様が明確に定義されている場合、実装クラス側で同じ説明を繰り返す必要がありません。重複を減らし、コメントの不整合を防げます。
ただし、実装クラス固有の注意点がある場合は、inheritdoc だけで済ませず、補足説明を書くことも検討します。
C#/// <inheritdoc />
/// <remarks>
/// この実装では、削除済みユーザーは検索対象に含まれません。
/// </remarks>
public User? Find(int id)
{
return null;
}
4. IntelliSenseに表示されるC#ドキュメントコメントの書き方
C#ドキュメントコメントの大きなメリットは、IntelliSenseに説明を表示できることです。メソッドを呼び出すとき、クラスを選択するとき、引数を入力するときに、コメントの内容が補助情報として表示されます。
IntelliSenseに表示されるコメントは、利用者が最も頻繁に目にするドキュメントです。そのため、長く詳しく書くことよりも、短時間で理解できることが重要です。
4-1. IntelliSenseに表示される場所と内容
IntelliSenseでは、主に次の内容が表示されます。
・クラスやメソッドのsummary
・メソッド引数のparam
・戻り値のreturns
・例外や補足説明の一部
・see crefで指定した型やメンバー
たとえば、次のようなメソッドがあるとします。
C#/// <summary>
/// 指定された注文IDの注文を確定します。
/// </summary>
/// <param name="orderId">確定する注文のID。</param>
/// <returns>確定後の注文情報。</returns>
public Order ConfirmOrder(int orderId)
{
return new Order();
}
利用者が ConfirmOrder を呼び出すと、メソッドの概要と引数の説明が表示されます。これにより、実装を開かなくても使い方を判断できます。
4-2. summaryを読みやすく書くコツ
IntelliSenseで最も目に入るのは summary です。summary は短く、具体的に書くのが基本です。
悪い例です。
C#/// <summary>
/// 処理します。
/// </summary>
public void Process()
{
}
何を処理するのか、いつ使うのかが分かりません。
よい例です。
C#/// <summary>
/// 未確定の注文を確定し、在庫数を更新します。
/// </summary>
public void ConfirmPendingOrders()
{
}
summary を書くときは、次の点を意識します。
・メソッド名をそのまま日本語にしただけの説明にしない
・重要な副作用があれば書く
・条件や制約がある場合は簡潔に触れる
・詳細はremarksに分ける
たとえば、在庫を更新する、メールを送信する、データベースに保存するなどの副作用は、利用者にとって重要な情報です。
C#/// <summary>
/// 注文を確定し、確認メールを送信します。
/// </summary>
このように、呼び出し側が知るべき挙動を含めると、IntelliSenseの価値が高まります。
4-3. param・returnsを表示させる実践例
param と returns は、メソッド呼び出し時の理解を助けます。
C#/// <summary>
/// 指定された期間内の売上合計を取得します。
/// </summary>
/// <param name="from">集計開始日。期間に含まれます。</param>
/// <param name="to">集計終了日。期間に含まれます。</param>
/// <returns>指定期間内の売上合計。売上がない場合は0。</returns>
public decimal GetSalesTotal(DateTime from, DateTime to)
{
return 0m;
}
この例では、from と to が期間に含まれるかどうかを書いています。日付範囲のメソッドでは、境界条件が非常に重要です。
別の例です。
C#/// <summary>
/// 指定されたキーワードで商品を検索します。
/// </summary>
/// <param name="keyword">検索キーワード。nullまたは空文字の場合はすべての商品を対象にします。</param>
/// <returns>条件に一致する商品一覧。該当がない場合は空の一覧。</returns>
public IReadOnlyList<Product> SearchProducts(string? keyword)
{
return Array.Empty<Product>();
}
このコメントでは、keyword が null や空文字のときの動作、該当なしの場合の戻り値が分かります。利用者は余計な確認をせずに安全に呼び出せます。
4-4. see crefで型やメソッドへのリンクを作る方法
see cref を使うと、コメント内で型やメソッドを参照できます。
C#/// <summary>
/// 注文を確定します。キャンセルする場合は <see cref="CancelOrder"/> を使用します。
/// </summary>
public void ConfirmOrder()
{
}
/// <summary>
/// 注文をキャンセルします。
/// </summary>
public void CancelOrder()
{
}
型を参照する場合は次のように書きます。
C#/// <summary>
/// <see cref="Order"/> の支払い状態を更新します。
/// </summary>
public void UpdatePaymentStatus(Order order)
{
}
同名メソッドが複数ある場合や、別クラスのメンバーを参照する場合は、名前空間や型名を含めると解決しやすくなります。
C#/// <summary>
/// <see cref="System.DateTime"/> を使用して期限日を計算します。
/// </summary>
cref の参照先が間違っていると警告が出ることがあります。リファクタリングで名前を変更した場合は、コメント内の参照も確認しましょう。
4-5. 外部ライブラリ利用時にもコメントを表示させる方法
自作ライブラリをDLLやNuGetパッケージとして利用する場合、XMLドキュメントファイルをDLLと一緒に配置すると、利用側のIDEでもコメントが表示されます。
たとえば、次のようにビルドするとします。
MyLibrary.dll
MyLibrary.xml
この MyLibrary.xml にドキュメントコメントが含まれていれば、参照先プロジェクトでIntelliSenseに説明が表示されます。
NuGetパッケージを作成する場合も、XMLドキュメントファイルをパッケージに含めることで、利用者がAPIの説明を見られるようになります。ライブラリを配布するなら、XMLファイルの同梱は重要な品質要素です。
4-6. IntelliSenseに表示されないときの確認ポイント
ドキュメントコメントを書いたのにIntelliSenseに表示されない場合は、次の点を確認します。
・コメントが対象メンバーの直前に書かれているか
・XMLタグが正しく閉じられているか
・summaryが空になっていないか
・参照先プロジェクトでXMLドキュメントファイルが存在するか
・DLLとXMLファイルが同じ場所に配置されているか
・NuGetパッケージにXMLファイルが含まれているか
・IDEのキャッシュやビルド状態が古くないか
同じソリューション内で参照している場合はすぐ表示されることが多いですが、外部DLLとして参照している場合はXMLファイルの配置が重要です。
また、コメントを書いた直後は、IDEの補完表示が更新されていないこともあります。再ビルドやIDEの再起動で解決する場合もあります。
5. XMLドキュメントファイルを出力する方法
C#ドキュメントコメントは、ビルド時にXMLドキュメントファイルとして出力できます。このXMLファイルには、クラス、メソッド、プロパティなどに書いたドキュメントコメントがまとめられます。
XMLドキュメントファイルを出力すると、DLL配布時のIntelliSense表示や、APIドキュメント生成に利用できます。ライブラリ開発では、ドキュメントコメントを書くだけでなく、XMLファイルを正しく出力して配布することが重要です。
5-1. XMLドキュメントファイルとは何か
XMLドキュメントファイルは、C#コンパイラがドキュメントコメントをもとに生成するXML形式のファイルです。
たとえば、次のようなコメントがあるとします。
C#/// <summary>
/// 商品を表します。
/// </summary>
public class Product
{
}
XMLドキュメントファイルには、概念的には次のような情報が出力されます。
XML<member name="T:Sample.Product">
<summary>
商品を表します。
</summary>
</member>
このXMLは、人が直接読むためというより、IDEやドキュメント生成ツールが利用するためのものです。DLLと一緒に配置することで、参照先プロジェクトでもコメント情報を利用できます。
5-2. Visual StudioでXMLドキュメントファイルを有効化する
Visual Studioでは、プロジェクトのプロパティからXMLドキュメントファイルの出力を有効にできます。
一般的な手順は次のとおりです。
1. プロジェクトを右クリックする
2. 「プロパティ」を開く
3. 「ビルド」または「出力」に関する設定を開く
4. XMLドキュメントファイルの生成を有効にする
5. プロジェクトをビルドする
有効化すると、ビルド出力先に .xml ファイルが生成されます。
bin/Debug/net8.0/MyLibrary.dll
bin/Debug/net8.0/MyLibrary.xml
このXMLファイルが、IntelliSenseやAPIドキュメント生成の元になります。
5-3. csprojでGenerateDocumentationFileを設定する
SDKスタイルのC#プロジェクトでは、.csproj に GenerateDocumentationFile を設定することでXMLドキュメントファイルを生成できます。
XML<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
プロジェクトファイル全体では、たとえば次のようになります。
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
出力ファイル名や出力先を細かく指定したい場合は、DocumentationFile を使うこともあります。
XML<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\MyLibrary.xml</DocumentationFile>
</PropertyGroup>
通常は GenerateDocumentationFile を有効にするだけで十分です。
5-4. ビルド時の警告とCS1591への対処
XMLドキュメントファイルの生成を有効にすると、publicな型やメンバーにドキュメントコメントがない場合、警告が出ることがあります。代表的なものがCS1591です。
CS1591は、公開されている型またはメンバーにXMLコメントがない場合に発生します。
Missing XML comment for publicly visible type or member
対処方法は主に3つあります。
・必要なpublic APIにドキュメントコメントを追加する
・公開する必要がないメンバーをinternalやprivateに変更する
・警告を抑制する
ライブラリ開発では、public APIにコメントを追加するのが基本です。一方、アプリケーション開発では、すべてのpublicメンバーにコメントを求めると負担が大きすぎる場合があります。その場合は、プロジェクト方針に応じて警告の扱いを決めます。
警告を抑制する例です。
XML<PropertyGroup>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
ただし、警告を抑制するとコメント不足に気づきにくくなります。公開APIの品質を重視するプロジェクトでは、抑制よりもコメント追加を優先するのがおすすめです。
5-5. NuGetパッケージやDLL配布時にXMLファイルを同梱する理由
NuGetパッケージやDLLを配布する場合、XMLドキュメントファイルを同梱すると、利用者のIDEでAPI説明が表示されます。
たとえば、ライブラリ利用者が次のメソッドを呼び出すとします。
C#client.SendMessage("hello");
XMLドキュメントファイルが同梱されていれば、SendMessage の概要、引数、戻り値、例外条件などがIntelliSenseで確認できます。
これは利用者にとって非常に重要です。READMEだけでは、すべてのクラスやメソッドの細かい仕様を確認するのが難しいためです。XMLコメントがあれば、コーディング中に必要な情報へすぐアクセスできます。
ライブラリ開発では、DLLだけでなくXMLファイルも成果物の一部として扱うとよいでしょう。
5-6. DocFXなどのドキュメント生成ツールとの連携
XMLドキュメントコメントは、DocFXなどのドキュメント生成ツールと組み合わせることで、HTML形式のAPIリファレンスに変換できます。
DocFXを使うと、C#プロジェクトのメタデータとXMLコメントをもとに、クラス、メソッド、プロパティなどのリファレンスページを生成できます。READMEやチュートリアルと組み合わせれば、利用者向けの開発者ドキュメントを整備できます。
一般的な使い分けは次のとおりです。
README:導入方法、概要、最初の使い方
XMLコメント:各APIの正確な仕様
DocFXなどのAPIドキュメント:体系化されたリファレンス
XMLコメントはAPIの近くにあるため、実装と一緒に更新しやすいのが利点です。一方で、全体像や設計思想はREADMEやガイド記事に書いた方が読みやすくなります。
6. 実務で使えるC#ドキュメントコメントのコード例
ここからは、実務でそのまま参考にしやすいC#ドキュメントコメントの例を紹介します。単にタグの使い方を示すだけでなく、どのような情報を書くと利用者にとって分かりやすいかを意識した例にしています。
6-1. クラスに書くドキュメントコメント例
クラスには、そのクラスが何を表すのか、どのような責務を持つのかを書きます。
C#/// <summary>
/// 注文の作成、確定、キャンセルを行うサービスです。
/// </summary>
/// <remarks>
/// このクラスは注文に関する業務操作を提供します。
/// 在庫管理や支払い処理の詳細は、それぞれ専用のサービスに委譲します。
/// </remarks>
public class OrderService
{
}
クラスコメントでは、細かいメソッド一覧を書く必要はありません。責務の範囲や、他のクラスとの関係を書くと、設計を理解しやすくなります。
悪い例です。
C#/// <summary>
/// 注文サービスクラスです。
/// </summary>
public class OrderService
{
}
この説明はクラス名を言い換えただけで、ほとんど情報が増えていません。
よい例です。
C#/// <summary>
/// 注文のライフサイクルを管理し、確定時に在庫引き当てを行います。
/// </summary>
public class OrderService
{
}
「在庫引き当てを行う」という重要な責務が分かります。
6-2. メソッドに書くドキュメントコメント例
メソッドには、目的、引数、戻り値、必要に応じて例外を書きます。
C#/// <summary>
/// 指定された注文を確定します。
/// </summary>
/// <param name="orderId">確定する注文のID。</param>
/// <returns>確定後の注文情報。</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="orderId"/> が1未満の場合にスローされます。
/// </exception>
/// <exception cref="InvalidOperationException">
/// 注文が存在しない、またはすでに確定済みの場合にスローされます。
/// </exception>
public Order ConfirmOrder(int orderId)
{
if (orderId < 1)
{
throw new ArgumentOutOfRangeException(nameof(orderId));
}
// 実装例
return new Order();
}
この例では、利用者が知りたい情報が明確です。
・何をするか
・どの注文を対象にするか
・何が返るか
・どのような場合に例外が出るか
特に、例外条件は実装を読まないと分かりにくいため、public APIでは書いておくと親切です。
6-3. プロパティに書くドキュメントコメント例
プロパティには、その値が何を表すのかを書きます。単位、範囲、既定値、null許容がある場合は明記します。
C#/// <summary>
/// 注文の合計金額を取得します。
/// </summary>
/// <value>税込の合計金額。明細が存在しない場合は0。</value>
public decimal TotalAmount { get; private set; }
設定可能なプロパティでは、許容範囲を書くと安全です。
C#/// <summary>
/// 1ページあたりの最大取得件数を取得または設定します。
/// </summary>
/// <value>1以上100以下の値。既定値は20。</value>
public int PageSize { get; set; } = 20;
nullになる可能性があるプロパティでは、その条件を書きます。
C#/// <summary>
/// 最後にログインした日時を取得します。
/// </summary>
/// <value>一度もログインしていない場合は null。</value>
public DateTime? LastLoginAt { get; private set; }
6-4. 非同期メソッドに書くドキュメントコメント例
非同期メソッドでは、戻り値が Task や Task<T> になります。returns には、タスクそのものではなく、非同期処理の結果として何が得られるのかを書きます。
C#/// <summary>
/// 指定されたユーザーIDに対応するユーザー情報を非同期で取得します。
/// </summary>
/// <param name="userId">取得対象のユーザーID。</param>
/// <param name="cancellationToken">操作をキャンセルするためのトークン。</param>
/// <returns>ユーザーが存在する場合はユーザー情報。それ以外の場合は null。</returns>
public async Task<User?> FindUserAsync(
int userId,
CancellationToken cancellationToken = default)
{
await Task.Delay(100, cancellationToken);
return null;
}
returns に「ユーザー情報を含むTask」と書くよりも、「ユーザーが存在する場合はユーザー情報」と書く方が利用者に伝わりやすくなります。
キャンセル可能な非同期メソッドでは、CancellationToken の意味も説明します。
C#/// <param name="cancellationToken">非同期処理のキャンセル要求を通知するトークン。</param>
例外を書く場合は、キャンセル時の例外や通信エラーなど、呼び出し側が考慮すべきものを明記します。
C#/// <exception cref="OperationCanceledException">
/// 操作がキャンセルされた場合にスローされます。
/// </exception>
6-5. ジェネリックメソッドに書くドキュメントコメント例
ジェネリックメソッドでは、typeparam を使って型パラメータの意味を書きます。
C#/// <summary>
/// 指定されたキーに対応する値を取得します。
/// </summary>
/// <typeparam name="TValue">取得する値の型。</typeparam>
/// <param name="key">値に対応するキー。</param>
/// <returns>値が存在する場合は指定された型の値。それ以外の場合は既定値。</returns>
public TValue? GetValue<TValue>(string key)
{
return default;
}
型パラメータが複数ある場合は、それぞれ説明します。
C#/// <summary>
/// 指定された値を別の型に変換します。
/// </summary>
/// <typeparam name="TSource">変換元の値の型。</typeparam>
/// <typeparam name="TDestination">変換後の値の型。</typeparam>
/// <param name="source">変換元の値。</param>
/// <returns>変換後の値。</returns>
public TDestination Convert<TSource, TDestination>(TSource source)
{
return default!;
}
TSource や TDestination は名前だけでもある程度意味が分かりますが、typeparam を書くことでAPIドキュメントとしての完成度が上がります。
6-6. 例外を明記したドキュメントコメント例
例外条件があるメソッドでは、exception を使って利用者に伝えます。
C#/// <summary>
/// 指定された金額を残高から引き落とします。
/// </summary>
/// <param name="amount">引き落とす金額。1以上を指定します。</param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="amount"/> が1未満の場合にスローされます。
/// </exception>
/// <exception cref="InvalidOperationException">
/// 残高が不足している場合にスローされます。
/// </exception>
public void Withdraw(decimal amount)
{
if (amount < 1)
{
throw new ArgumentOutOfRangeException(nameof(amount));
}
if (Balance < amount)
{
throw new InvalidOperationException("残高が不足しています。");
}
Balance -= amount;
}
/// <summary>
/// 現在の残高を取得します。
/// </summary>
public decimal Balance { get; private set; }
例外コメントを書くときは、実装内部で一時的に発生する例外をすべて列挙するのではなく、呼び出し側が対処すべき例外を優先します。
たとえば、引数エラー、状態不正、キャンセル、認証エラー、外部サービスへのアクセス失敗などは、利用者にとって重要です。
6-7. 悪い例と良い例の比較
ドキュメントコメントは、書けば必ずよくなるわけではありません。内容が薄いコメントや、実装と重複しているコメントは保守性を下げます。
悪い例です。
C#/// <summary>
/// ユーザーを取得します。
/// </summary>
/// <param name="id">id。</param>
/// <returns>ユーザー。</returns>
public User? GetUser(int id)
{
return null;
}
このコメントでは、ほとんどの情報がメソッド名や型から分かる内容です。id が何のIDなのか、存在しない場合にどうなるのかが分かりません。
よい例です。
C#/// <summary>
/// 指定されたユーザーIDに対応する有効なユーザーを取得します。
/// </summary>
/// <param name="id">ユーザーID。</param>
/// <returns>有効なユーザーが存在する場合はユーザー情報。それ以外の場合は null。</returns>
public User? GetUser(int id)
{
return null;
}
「有効なユーザー」が対象であること、存在しない場合に null になることが分かります。
もう一つの悪い例です。
C#/// <summary>
/// price と quantity を掛け算します。
/// </summary>
public decimal CalculateTotal(decimal price, int quantity)
{
return price * quantity;
}
これは実装を説明しているだけです。利用者が知りたいのは、業務上何の金額なのか、税込か税抜か、数量の制約はあるかです。
よい例です。
C#/// <summary>
/// 税抜単価と数量から明細行の小計金額を計算します。
/// </summary>
/// <param name="price">税抜単価。</param>
/// <param name="quantity">購入数量。1以上を指定します。</param>
/// <returns>税抜の小計金額。</returns>
public decimal CalculateSubtotal(decimal price, int quantity)
{
return price * quantity;
}
このように、ドキュメントコメントでは「コードを見れば分かること」ではなく「使う人が判断に迷うこと」を書くのがポイントです。
7. 保守しやすいC#ドキュメントコメントの書き方
C#ドキュメントコメントは、書いた瞬間だけでなく、将来の保守まで考える必要があります。実装が変わったのにコメントが古いままだと、コメントは助けではなく誤情報になります。
保守しやすいコメントを書くには、量より質が大切です。すべてを説明しようとするのではなく、利用者にとって重要で、かつコードからは読み取りにくい情報に絞って書きます。
7-1. コードを見れば分かる説明を書かない
ドキュメントコメントでよくある失敗は、メソッド名や型をそのまま言い換えるだけの説明です。
C#/// <summary>
/// 名前を取得します。
/// </summary>
public string Name { get; set; }
この程度であれば、プロパティ名から分かります。もちろんpublic APIとしてコメントが必要な場合もありますが、内容が薄いままでは利用者の助けになりません。
改善するなら、値の意味や制約を書きます。
C#/// <summary>
/// 表示用のユーザー名を取得または設定します。
/// </summary>
/// <value>画面表示に使用される名前。未設定の場合はメールアドレスが表示名として使用されます。</value>
public string? Name { get; set; }
コードを見れば分かることではなく、コードだけでは分かりにくい仕様を書きましょう。
7-2. 「何をするか」より「なぜそうするか」を補足する
メソッド名から「何をするか」は分かることが多いです。ドキュメントコメントで価値が出やすいのは、「なぜその挙動なのか」「どのような前提なのか」です。
たとえば、次のようなコメントは実装説明に寄っています。
C#/// <summary>
/// 注文一覧を日付の降順で並べ替えます。
/// </summary>
必要に応じて、なぜ降順なのかを書くと意図が伝わります。
C#/// <summary>
/// 注文一覧を、利用者が直近の注文を確認しやすいように注文日の降順で並べ替えます。
/// </summary>
ただし、すべてのコメントに理由を書く必要はありません。業務ルール、設計判断、互換性維持など、将来の変更時に判断材料になる内容を優先します。
7-3. 引数・戻り値・例外は利用者目線で書く
引数、戻り値、例外は、メソッドを呼び出す側に直接影響します。利用者目線で、どう渡せばよいか、何が返るか、何に注意すべきかを書きます。
悪い例です。
C#/// <param name="status">ステータス。</param>
よい例です。
C#/// <param name="status">更新後の注文状態。キャンセル済みの注文には <c>OrderStatus.Confirmed</c> を指定できません。</param>
戻り値も同じです。
悪い例です。
C#/// <returns>結果。</returns>
よい例です。
C#/// <returns>更新に成功した場合は true。対象の注文が存在しない場合は false。</returns>
例外も、型名だけでなく発生条件を書きます。
C#/// <exception cref="InvalidOperationException">
/// 注文がすでにキャンセル済みの場合にスローされます。
/// </exception>
利用者が呼び出し前に避けられる条件、呼び出し後にハンドリングすべき条件を明確にすることが重要です。
7-4. 実装変更で古くなりやすいコメントを避ける
コメントは実装よりも更新されにくい傾向があります。そのため、実装の細かい手順を書きすぎると、変更時にズレが生じやすくなります。
避けたい例です。
C#/// <summary>
/// SQLを3回実行し、結果をListに詰めて返します。
/// </summary>
実装が変わってSQLが1回になったり、キャッシュを使うようになったりすると、コメントが古くなります。
改善例です。
C#/// <summary>
/// 指定された条件に一致する注文一覧を取得します。
/// </summary>
/// <remarks>
/// 取得結果にはキャンセル済みの注文は含まれません。
/// </remarks>
外部から見た仕様を中心に書けば、内部実装が変わってもコメントが古くなりにくくなります。
もちろん、パフォーマンス上の理由で「複数回の外部API呼び出しが発生する」などを伝える必要がある場合は書くべきです。その場合も、実装手順ではなく利用者に影響する点として説明します。
7-5. public APIとinternal/privateで書き分ける
すべてのメンバーに同じ粒度でドキュメントコメントを書く必要はありません。重要なのは、誰がそのメンバーを使うのかです。
public APIは、外部の利用者が使う可能性があります。そのため、引数、戻り値、例外、注意点を丁寧に書く価値があります。
C#/// <summary>
/// 指定された注文IDに対応する注文を取得します。
/// </summary>
/// <param name="orderId">注文ID。</param>
/// <returns>注文が存在する場合は注文情報。それ以外の場合は null。</returns>
public Order? FindOrder(int orderId)
{
return null;
}
internalメンバーは、同じアセンブリ内の開発者が使います。複雑な業務ルールや注意点がある場合はコメントを書くとよいでしょう。
privateメソッドは、基本的にコードを読めば用途が分かるように命名し、必要な場合だけコメントを書きます。
C#// 外部システムの仕様により、0円明細は送信対象から除外する。
private bool ShouldSend(OrderLine line)
{
return line.Amount > 0;
}
privateメソッドにXMLドキュメントコメントを書くこともできますが、機械的にすべて書くより、必要な箇所に通常コメントを書く方が読みやすい場合もあります。
7-6. チーム開発でコメント品質を揃えるルール
チーム開発では、コメントの書き方が人によって大きく異なると、コード全体の読みやすさが下がります。最低限のルールを決めておくと品質を揃えやすくなります。
たとえば、次のようなルールです。
・publicなクラスとメソッドにはsummaryを書く
・publicメソッドの引数にはparamを書く
・戻り値がboolの場合はtrue/falseの意味を書く
・nullを返す可能性がある場合はreturnsに書く
・業務例外や呼び出し側が対処すべき例外はexceptionに書く
・summaryにはメソッド名の言い換えだけを書かない
・自動生成された空コメントを残さない
さらに、レビュー観点として次のようなチェック項目を用意すると効果的です。
・利用者が実装を読まずに使えるか
・コメントとコードに矛盾がないか
・古くなりやすい実装詳細を書きすぎていないか
・引数の制約や戻り値の特殊ケースが書かれているか
ルールは厳しすぎると形骸化します。プロジェクトの規模や公開範囲に合わせて、運用しやすい範囲から始めるのが現実的です。
7-7. 自動生成コメントに頼りすぎないための判断基準
Visual Studioの /// テンプレートは便利ですが、自動生成された枠を埋めるだけではよいコメントになりません。
自動生成コメントに頼りすぎると、次のようなコメントが増えがちです。
C#/// <summary>
/// Gets the user.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>The user.</returns>
このようなコメントは、形式は整っていますが情報量が少ないです。
コメントを書くときは、次の質問に答えられるか確認します。
・このメソッドはどのような場面で使うのか
・引数に指定してはいけない値はあるか
・nullや空文字はどう扱うのか
・戻り値がnullや空になる条件はあるか
・例外が発生する条件はあるか
・副作用はあるか
・呼び出し順序に制約はあるか
これらに該当する情報がある場合、ドキュメントコメントに書く価値があります。逆に、名前と型から明らかで、補足すべき仕様がない場合は、無理に長く書く必要はありません。
8. C#ドキュメントコメントでよくあるエラーとトラブル対処
C#ドキュメントコメントはXML形式で書くため、通常のコメントとは違ったエラーや警告が発生することがあります。特に、XMLタグの閉じ忘れ、引数名の不一致、cref の参照エラー、ジェネリック型の書き方には注意が必要です。
ここでは、実務でよく遭遇するトラブルと対処方法を紹介します。
8-1. XMLタグの閉じ忘れ・エスケープ漏れ
最も多いのは、XMLタグの閉じ忘れです。
悪い例です。
C#/// <summary>
/// 注文を確定します。
public void Confirm()
{
}
正しい例です。
C#/// <summary>
/// 注文を確定します。
/// </summary>
public void Confirm()
{
}
また、<、>、& などの文字をそのまま書くとXMLとして解釈されることがあります。
悪い例です。
C#/// <summary>
/// quantity < 1 の場合は例外をスローします。
/// </summary>
正しい例です。
C#/// <summary>
/// quantity < 1 の場合は例外をスローします。
/// </summary>
コード中の識別子や値を示したい場合は、<c> を使うと読みやすくなります。
C#/// <summary>
/// <c>quantity</c> が1未満の場合は例外をスローします。
/// </summary>
8-2. param名と実際の引数名が一致しない
param の name 属性は、実際の引数名と一致している必要があります。
悪い例です。
C#/// <summary>
/// 商品を追加します。
/// </summary>
/// <param name="productCode">商品コード。</param>
public void AddProduct(string code)
{
}
この場合、実際の引数名は code なのに、コメントでは productCode になっています。
正しい例です。
C#/// <summary>
/// 商品を追加します。
/// </summary>
/// <param name="code">商品コード。</param>
public void AddProduct(string code)
{
}
リファクタリングで引数名を変更したときに、コメント側が古いまま残ることがあります。IDEのリネーム機能を使うと、コメント内の参照も更新される場合がありますが、最終的にはビルド警告やレビューで確認することが重要です。
8-3. crefの参照先が解決できない
see cref や exception cref の参照先が存在しない場合、警告が出ることがあります。
悪い例です。
C#/// <summary>
/// <see cref="Cancel"/> を呼び出すと注文をキャンセルできます。
/// </summary>
public void Confirm()
{
}
Cancel というメソッドが存在しない場合、参照を解決できません。
正しい例です。
C#/// <summary>
/// <see cref="CancelOrder"/> を呼び出すと注文をキャンセルできます。
/// </summary>
public void Confirm()
{
}
public void CancelOrder()
{
}
同名メソッドが複数ある場合は、シグネチャを含めた参照が必要になることがあります。
C#/// <summary>
/// 文字列を指定して検索する場合は <see cref="Search(string)"/> を使用します。
/// </summary>
public void SearchByKeyword(string keyword)
{
}
型が別名前空間にある場合は、完全修飾名を使うと解決しやすくなります。
C#/// <summary>
/// <see cref="System.DateTime"/> を使用して期限を表します。
/// </summary>
8-4. ジェネリック型の書き方でエラーになる
XMLドキュメントコメントでジェネリック型を書くとき、<T> のようにそのまま書くとXMLタグとして解釈されることがあります。
悪い例です。
C#/// <summary>
/// List<T> を返します。
/// </summary>
正しい例です。
C#/// <summary>
/// <see cref="List{T}"/> を返します。
/// </summary>
または、単純な文章ならエスケープして書くこともできます。
C#/// <summary>
/// List<T> を返します。
/// </summary>
型パラメータ自体を参照する場合は、typeparamref を使うと分かりやすくなります。
C#/// <summary>
/// <typeparamref name="T"/> 型の値を取得します。
/// </summary>
/// <typeparam name="T">取得する値の型。</typeparam>
public T? Get<T>(string key)
{
return default;
}
ジェネリック型の cref は書き方に癖があるため、IDEの補完やビルド警告を確認しながら調整するとよいでしょう。
8-5. XMLドキュメントの警告を無視してよいケース
XMLドキュメントファイルの生成を有効にすると、コメント不足やXML構文エラーに関する警告が表示されます。基本的には対応するのが望ましいですが、すべての警告を必ず修正すべきとは限りません。
たとえば、アプリケーション内部だけで使うプロジェクトで、publicメンバーが外部APIとして公開されない場合、すべてにコメントを書く負担が大きいことがあります。この場合は、CS1591を抑制する選択もあります。
XML<PropertyGroup>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
ただし、次のようなケースでは警告を無視しない方がよいです。
・NuGetパッケージとして公開するライブラリ
・社内共通ライブラリ
・SDKやAPIクライアント
・外部チームが参照するアセンブリ
・DocFXなどでAPIドキュメントを生成するプロジェクト
警告を抑制するかどうかは、公開範囲と保守方針で判断します。公開APIでは、警告を品質改善のきっかけとして扱うのがおすすめです。
8-6. コメントが冗長になって読みづらいときの改善方法
ドキュメントコメントは、詳しく書きすぎても読みづらくなります。特にIntelliSenseでは、長すぎる説明はかえって邪魔になることがあります。
冗長な例です。
C#/// <summary>
/// このメソッドは、引数として渡された注文IDを使用してデータベースから注文を検索し、
/// 注文が見つかった場合にはその注文オブジェクトを返し、見つからなかった場合にはnullを返す処理を行います。
/// </summary>
public Order? FindOrder(int orderId)
{
return null;
}
改善例です。
C#/// <summary>
/// 指定された注文IDに対応する注文を取得します。
/// </summary>
/// <param name="orderId">注文ID。</param>
/// <returns>注文が存在する場合は注文情報。それ以外の場合は null。</returns>
public Order? FindOrder(int orderId)
{
return null;
}
改善のポイントは、情報をタグごとに分けることです。概要は summary、引数は param、戻り値は returns、詳細は remarks に分けると読みやすくなります。
また、長い説明が必要な場合は、summary に詰め込まず remarks に移します。
C#/// <summary>
/// 注文を確定します。
/// </summary>
/// <remarks>
/// 確定時には在庫引き当てと支払い状態の更新を行います。
/// 在庫不足の場合、注文は確定されません。
/// </remarks>
public void ConfirmOrder()
{
}
9. C#ドキュメントコメントの運用・自動化
ドキュメントコメントは、個人の努力だけで品質を維持するのが難しい場合があります。チーム開発では、レビュー、静的解析、CI、ドキュメント生成ツールを組み合わせて運用すると効果的です。
特に、公開APIが多いプロジェクトでは、コメントの不足や不整合を早い段階で検出できる仕組みが重要です。
9-1. StyleCopやRoslyn Analyzerでコメントルールをチェックする
StyleCopやRoslyn Analyzerを使うと、ドキュメントコメントの有無や形式をチェックできます。
たとえば、次のようなルールを検出できます。
・publicメンバーにsummaryがない
・paramが不足している
・param名が実引数と一致していない
・returnsが不足している
・XML構文が正しくない
Analyzerを導入すると、IDE上で警告が表示されるため、ビルド前に問題に気づきやすくなります。また、チーム全体で同じルールを共有できるため、コメント品質を揃えやすくなります。
ただし、ルールを厳しくしすぎると、意味の薄いコメントが量産されることがあります。最初はpublic APIを中心にチェックし、必要に応じて対象範囲を広げるとよいでしょう。
9-2. CIでドキュメントコメントの不足を検出する
CIでビルドや静的解析を実行すれば、ドキュメントコメントの不足をプルリクエスト段階で検出できます。
たとえば、次のような運用が考えられます。
・XMLドキュメントファイルの生成を有効にする
・public APIのコメント不足を警告として検出する
・重要なプロジェクトでは警告をエラー扱いにする
・DocFXのビルドをCIで実行し、ドキュメント生成エラーを検出する
警告をエラー扱いにする場合は、プロジェクト全体の準備が必要です。既存コードにコメント不足が多い状態でいきなり厳格化すると、開発が止まりやすくなります。
現実的には、新規追加や変更されたpublic APIからルールを適用し、既存コードは段階的に改善する方法が運用しやすいです。
9-3. DocFXでAPIリファレンスを自動生成する
DocFXを使うと、C#プロジェクトからAPIリファレンスを自動生成できます。XMLドキュメントコメントに書いた内容が、HTMLドキュメントとして表示されます。
APIリファレンスを生成する流れは、概念的には次のようになります。
1. C#コードにXMLドキュメントコメントを書く
2. XMLドキュメントファイルを生成する
3. DocFXでメタデータを抽出する
4. HTMLドキュメントを生成する
5. 社内サイトや公開サイトでホスティングする
DocFXを活用すると、ソースコードに近い場所でAPI仕様を管理できます。コード変更とコメント変更を同じプルリクエストで確認できるため、ドキュメントの更新漏れを減らせます。
ただし、APIリファレンスだけでは、ライブラリ全体の使い方や設計思想は伝わりにくいことがあります。チュートリアル、README、サンプルコードもあわせて整備すると効果的です。
9-4. ライブラリ開発でREADME・XMLコメント・APIドキュメントを使い分ける
ライブラリ開発では、ドキュメントの種類ごとに役割を分けると分かりやすくなります。
READMEには、導入方法や最初の使い方を書きます。
・ライブラリの概要
・インストール方法
・最小サンプルコード
・よく使う機能
・設定方法
XMLドキュメントコメントには、各APIの正確な仕様を書きます。
・クラスの責務
・メソッドの概要
・引数の意味
・戻り値の条件
・例外
・注意点
APIドキュメントには、XMLコメントをもとにしたリファレンスを掲載します。
・名前空間一覧
・クラス一覧
・メソッド一覧
・プロパティ一覧
・継承関係
すべてをXMLコメントに書こうとすると、コメントが長くなりすぎます。逆に、READMEだけに細かいAPI仕様を書くと、コード変更とズレやすくなります。それぞれの役割を分けることで、保守しやすいドキュメントになります。
9-5. 既存コードへ段階的にドキュメントコメントを追加する手順
既存コードにドキュメントコメントが少ない場合、一度にすべて追加しようとすると大きな負担になります。段階的に進めるのがおすすめです。
まず、対象範囲を決めます。
・外部公開しているpublic API
・社内共通ライブラリのpublicメンバー
・利用頻度の高いクラス
・問い合わせが多いメソッド
・仕様が複雑な処理
次に、最低限のコメントルールを決めます。
・summaryは必須
・引数があるpublicメソッドはparamを書く
・戻り値があるpublicメソッドはreturnsを書く
・例外条件が重要な場合はexceptionを書く
そのうえで、新規コードからルールを適用します。既存コードは、変更が入ったタイミングや、APIドキュメント化が必要になったタイミングで追加していきます。
段階的に進める例です。
1. XMLドキュメントファイルの生成を有効にする
2. 現在の警告数を把握する
3. 外部公開APIからコメントを追加する
4. 新規追加APIには必ずコメントを書くルールにする
5. CIで新たな不足を検出する
6. DocFXなどでAPIドキュメント化する
完璧を目指すより、重要なAPIから確実に整備していく方が継続しやすくなります。
10. C#ドキュメントコメントのよくある質問
最後に、C#ドキュメントコメントに関するよくある質問に答えます。実務では、どこまで書くべきか、privateメソッドにも必要か、日本語と英語のどちらがよいかなどで迷うことが多いです。
10-1. すべてのメソッドにsummaryを書くべき?
必ずしも、すべてのメソッドに summary を書く必要はありません。重要なのは、誰がそのメソッドを使うかです。
public APIや、他の開発者が頻繁に利用するメソッドには summary を書くべきです。特にライブラリやSDKでは、publicなクラスやメソッドにドキュメントコメントがあることが望ましいです。
一方、privateメソッドや短い補助メソッドにまで機械的に summary を書くと、内容の薄いコメントが増えることがあります。
判断基準は次のとおりです。
・外部から使われるなら書く
・使い方に迷うなら書く
・業務ルールがあるなら書く
・引数や戻り値に注意点があるなら書く
・名前だけで十分明確なprivateメソッドなら省略してもよい
10-2. privateメソッドにもドキュメントコメントは必要?
privateメソッドには、通常のXMLドキュメントコメントが必須とは限りません。privateメソッドは同じクラス内だけで使われるため、分かりやすい命名と小さな責務に分割することの方が重要です。
ただし、次のようなprivateメソッドにはコメントを書く価値があります。
・複雑な業務ルールを扱う
・外部システムの制約に合わせている
・一見すると不自然な処理をしている
・アルゴリズムの意図を説明する必要がある
・将来変更すると危険な前提がある
この場合、/// のXMLコメントではなく、通常の // コメントで意図を補足する方が自然なこともあります。
C#// 外部決済サービスの仕様により、キャンセル済み注文は送信対象から除外する。
private bool ShouldSendToPaymentProvider(Order order)
{
return order.Status != OrderStatus.Canceled;
}
privateメソッドでは、形式よりも保守に役立つ説明であることを重視しましょう。
10-3. 日本語と英語のどちらで書くべき?
日本語で書くか英語で書くかは、プロジェクトの利用者に合わせて決めます。
日本国内のチームだけで開発し、利用者も日本語話者であれば、日本語コメントで問題ありません。業務ルールやドメイン知識は、日本語の方が正確に伝わることも多いです。
一方、OSSとして公開するライブラリ、海外チームと共同開発するプロジェクト、英語圏の利用者が想定されるSDKでは、英語で書く方が適しています。
判断基準は次のとおりです。
・利用者が日本語話者中心なら日本語
・OSSや海外利用者を想定するなら英語
・社内標準があるならそれに従う
・ドメイン用語の表記ゆれを避ける
重要なのは、プロジェクト内で統一することです。日本語と英語が混在していると読みにくくなるため、チームでルールを決めておきましょう。
10-4. コメントと実装がズレないようにするには?
コメントと実装のズレを完全になくすことは難しいですが、次の工夫で減らせます。
・実装の細かい手順を書きすぎない
・外部から見た仕様を中心に書く
・コード変更時にコメントもレビュー対象にする
・テスト名や仕様書とも整合性を確認する
・public APIの変更時はXMLコメント更新を必須にする
・DocFXなどで生成されるドキュメントを定期的に確認する
特に大切なのは、プルリクエストのレビューでコメントも確認することです。コードだけでなく、ドキュメントコメントが実際の挙動と合っているかを見る習慣を作ると、ズレを早めに発見できます。
また、コメントに実装手順を書きすぎないことも重要です。内部処理は変わりやすいため、利用者に見える仕様を中心に書くと、コメントが古くなりにくくなります。
10-5. ///コメントと//コメントはどう使い分ける?
/// コメントは、クラスやメソッドなどのAPI利用者に向けた説明に使います。IntelliSenseやXMLドキュメントファイルに出力されるため、外部から参照される情報に向いています。
C#/// <summary>
/// 指定された注文IDに対応する注文を取得します。
/// </summary>
/// <param name="orderId">注文ID。</param>
/// <returns>注文が存在する場合は注文情報。それ以外の場合は null。</returns>
public Order? FindOrder(int orderId)
{
return null;
}
// コメントは、実装を読む開発者に向けた補足に使います。
C#// 既存仕様との互換性を保つため、削除済み注文も検索対象に含める。
var orders = GetOrders(includeDeleted: true);
使い分けの目安は次のとおりです。
///:APIの使い方、引数、戻り値、例外、公開仕様
//:実装上の意図、注意点、なぜそうしているか
API利用者が知るべきことは ///、実装保守者が知るべきことは // と考えると分かりやすいです。
10-6. XMLドキュメントコメントだけで仕様書は作れる?
XMLドキュメントコメントだけで、ある程度のAPI仕様書は作れます。DocFXなどのツールを使えば、クラス、メソッド、プロパティのリファレンスを自動生成できます。
ただし、XMLドキュメントコメントだけですべての仕様を表現するのは難しいです。特に、次のような情報は別のドキュメントに書いた方が分かりやすくなります。
・システム全体の概要
・導入手順
・認証や設定方法
・代表的なユースケース
・設計思想
・チュートリアル
・画面仕様や業務フロー
XMLドキュメントコメントは、各APIの正確な説明に向いています。一方で、全体像や使い始めの導線はREADME、設計書、チュートリアル記事に書く方が適しています。
理想的には、次のように役割分担します。
README:最初に読む導入ガイド
XMLコメント:APIごとの仕様
DocFXなど:APIリファレンス
設計書:全体構成や設計判断
XMLドキュメントコメントは仕様書の一部として非常に有用ですが、それだけで完結させようとせず、目的に応じて他のドキュメントと組み合わせるのがおすすめです。
まとめ
C#ドキュメントコメントは、/// とXMLタグを使って、クラスやメソッドの説明を構造化して書く仕組みです。単なるメモではなく、IntelliSenseへの表示、XMLドキュメントファイルの出力、APIリファレンス生成、NuGetパッケージ利用者への説明など、さまざまな場面で活用できます。
まず覚えるべき基本タグは、summary、param、returns です。クラスやメソッドの概要、引数の意味、戻り値の条件を明確にするだけでも、APIの使いやすさは大きく向上します。必要に応じて、remarks、exception、value、example、see、typeparam、inheritdoc などを使い分けると、より実用的なドキュメントになります。
大切なのは、コードを見れば分かることを繰り返すのではなく、利用者が判断に迷う情報を書くことです。引数に指定できる値、nullや空コレクションの扱い、戻り値の意味、例外が発生する条件、副作用、業務ルールなどは、ドキュメントコメントに書く価値があります。
一方で、実装の細かい手順を書きすぎると、コード変更時にコメントが古くなりやすくなります。保守しやすいコメントにするには、外部から見た仕様を中心に書き、詳細な補足は remarks に分け、チームでコメント品質のルールを共有することが重要です。
ライブラリやSDKを開発する場合は、XMLドキュメントファイルの生成を有効にし、DLLやNuGetパッケージに同梱しましょう。さらにDocFXなどのツールと連携すれば、ソースコードに近い場所で管理されたAPIリファレンスを自動生成できます。
C#ドキュメントコメントは、書き方を覚えるだけなら難しくありません。しかし、実務で本当に役立つコメントにするには、利用者目線と保守性の両方が必要です。public APIや重要な業務ロジックから少しずつ整備し、コードとドキュメントが一体となった読みやすいC#プロジェクトを目指しましょう。

