C#コーディング規約の決め方|命名ルール・設計方針・チームで守るべき実践例

はじめに

C#で開発を続けていると、同じ処理でも開発者によって書き方が異なる場面が出てきます。クラス名や変数名、波括弧の位置、varの使い方、例外処理、非同期メソッドの設計など、選択肢が多いことがC#の柔軟さである一方、チーム開発ではコードの不統一につながります。

C#コーディング規約は、コードを一つの書き方に縛るためのものではありません。誰が読んでも意図を理解しやすくし、修正やレビュー、テストを効率化するための共通ルールです。

本記事では、C#コーディング規約を決める際に押さえたい命名規則、書式、設計方針、チーム運用、自動化の方法を、具体例とテンプレートを交えて解説します。

1. C#コーディング規約とは?まず押さえるべき基本

1-1. コーディング規約の目的は「正しさ」よりも「読みやすさと保守性」

コーディング規約を守っているからといって、プログラムが正しく動作するとは限りません。反対に、規約に違反したコードでも、コンパイルや実行は可能です。

規約の主な目的は、コードの読みやすさと保守性を高めることです。

同じプロジェクト内で命名や書式が統一されていれば、開発者はコードの見た目を解読することに時間を使わず、処理内容や設計上の問題に集中できます。また、担当者が変わった場合でも、一定のルールに沿って書かれたコードは理解しやすくなります。

C#コーディング規約によって期待できる主な効果は、次のとおりです。

  • コードを読む時間が短くなる

  • コードレビューの指摘が統一される

  • 修正時の影響範囲を把握しやすくなる

  • 新しいメンバーがプロジェクトに参加しやすくなる

  • 自動解析や自動整形を導入しやすくなる

  • 個人の好みによる議論を減らせる

規約は「絶対に正しい書き方」を定義するものではなく、チーム内の判断基準をそろえるためのものと考えることが大切です。

1-2. C#の規約で決める主な範囲

C#コーディング規約では、一般的に次のような項目を決めます。

命名規則

クラス、メソッド、プロパティ、変数、定数、インターフェイスなどの名前の付け方を定義します。

書式ルール

インデント、改行、スペース、波括弧、usingの順序、1行に記述する内容などを決めます。

言語機能の使い方

var、LINQ、ラムダ式、パターンマッチング、null許容参照型、asyncawaitなどを、どのような場面で使うかを定義します。

設計方針

クラスの責務、メソッドの長さ、依存関係、例外処理、DTOやServiceなどの役割分担を決めます。

コメントとドキュメント

通常コメントやXMLドキュメントコメントを、どの範囲まで記述するかを決めます。

テストとレビュー

テストコードの命名、レビュー時の確認項目、規約違反の扱い方を定義します。

すべてを一度に細かく決める必要はありません。最初は、命名規則、書式、自動チェック、例外処理など、コード全体への影響が大きい項目から決めると運用しやすくなります。

1-3. Microsoft公式規約・チーム独自ルール・プロジェクトルールの違い

C#の規約を考える際は、次の3種類を分けて考える必要があります。

Microsoftや.NETで一般的に採用される規約

.NETのAPI設計やC#の一般的な記述方法に合わせたルールです。たとえば、公開クラスや公開メソッドにPascalCaseを使うこと、インターフェイス名をIから始めることなどが該当します。

チーム独自ルール

所属組織や開発チーム全体で共通化するルールです。privateフィールドにアンダースコアを付けるか、varをどこまで使うか、1メソッドの行数をどの程度にするかといった項目が含まれます。

プロジェクト固有ルール

特定のシステムの事情に合わせたルールです。たとえば、レイヤー構成、名前空間、DTOの命名、ログ出力、例外の種類、外部APIとの連携方法などが該当します。

基本的にはMicrosoftや.NETで広く使われる慣習を土台にし、必要な部分だけチームやプロジェクトのルールを追加する方法が適しています。独自ルールを増やしすぎると、新規参加者が理解しにくくなり、自動化ツールとも連携しにくくなります。

1-4. 規約を決めないことで起こる問題

C#コーディング規約を決めずに開発すると、次のような問題が起こりやすくなります。

同じ意味のクラスが、UserServiceUsersManagerUserLogicなど異なる基準で命名され、役割を名前から判断できなくなります。privateフィールドも、userRepository_userRepositorym_userRepositoryが混在する可能性があります。

また、コードレビューで「この書き方のほうが好み」「自分はvarを使わない」といった本質的でない議論が増えます。レビュー担当者によって指摘内容が変われば、開発者は何を基準に修正すべきか判断できません。

書式の違いによる変更差分も問題です。機能を1行修正しただけなのに、ファイル全体が整形されると、レビューで本来確認すべき変更を見つけにくくなります。

規約がない状態は自由に見えますが、実際には各開発者が毎回判断しなければならず、チーム全体の認知負荷を高めます。

2. C#コーディング規約を決める前に整理すべき方針

2-1. 既存コードに合わせるか、標準規約に寄せるか

既存プロジェクトにC#コーディング規約を導入する場合、標準的な規約に統一することだけが正解ではありません。すでに大量のコードが存在するなら、既存コードとの一貫性も重要です。

一部のファイルだけ新しい規約へ変更すると、同じプロジェクト内に複数の書き方が混在します。規約変更による大規模な差分が、機能修正の履歴やgit blameの追跡を難しくすることもあります。

判断するときは、次の点を確認します。

  • 現在の書き方に明確な一貫性があるか

  • 既存コードの量はどの程度か

  • 自動変換できるルールか

  • 変更によるレビュー負担は許容できるか

  • 今後の保守期間は長いか

  • 外部ライブラリや生成コードへの影響はないか

既存の規約に重大な問題がなければ、その書き方を明文化する方法も有効です。変更する場合は、機能改修と規約変更を別のコミットやプルリクエストに分けると、差分を確認しやすくなります。

2-2. 個人の好みではなくチームの開発効率で判断する

規約の議論では、「自分はこの書き方が読みやすい」という意見が出やすくなります。しかし、個人の好みだけではチーム共通の基準を作れません。

判断基準にすべきなのは、次のような要素です。

  • 初見の開発者が理解しやすいか

  • IDEや自動整形ツールで統一できるか

  • コードレビューの負担を減らせるか

  • 一般的なC#の慣習から大きく外れていないか

  • 例外条件を説明しやすいか

  • 長期的な保守に役立つか

複数の書き方に大きな優劣がない場合は、議論を続けるより、一般的な慣習やツールのデフォルトに合わせるほうが効率的です。

規約の目的は全員の好みを一致させることではなく、判断を繰り返さなくて済む状態を作ることです。

2-3. 厳格に守るルールと例外を許すルールを分ける

すべての規約を同じ強さで運用すると、重要度の低い違反まで修正対象になり、開発速度が低下します。

規約は、たとえば次の3段階に分けると運用しやすくなります。

必須

違反すると不具合、セキュリティ問題、重大な保守性低下につながるルールです。CIでエラーとして扱う候補になります。

推奨

原則として守るものの、合理的な理由があれば例外を認めるルールです。コードレビューでは理由を確認します。

任意

統一できれば望ましいものの、機能や保守性への影響が小さいルールです。自動整形に任せ、人が細かく指摘しない運用が適しています。

たとえば、未処理の例外を握りつぶすコードは禁止、公開APIの命名規則は必須、varの使用基準は推奨、空行の細かな位置は自動整形に任せる、といった分け方ができます。

2-4. 新規開発・既存改修・OSS利用で規約を変えるべきケース

規約はプロジェクトの状況によって調整する必要があります。

新規開発では、標準的なC#コーディング規約に寄せやすく、null許容参照型やAnalyzer、自動整形も最初から導入できます。

既存改修では、既存コードとの一貫性と差分の小ささを優先します。全面的な統一ではなく、変更した範囲から段階的に適用する方法が現実的です。

OSSへコントリビュートする場合は、自社や個人の規約ではなく、対象リポジトリの規約を優先します。.editorconfig、コントリビューションガイド、既存コード、CI設定などを確認してから変更します。

コード生成ツールが出力するファイルや外部から取り込んだコードは、通常の規約チェックから除外することも検討します。人が保守するコードと自動生成コードを同じ基準で評価すると、不要な警告が増えるためです。

3. C#の命名規則:最初に決めるべき基本ルール

3-1. クラス名・構造体名・列挙型名はPascalCaseにする

クラス、構造体、レコード、列挙型などの型名には、一般的にPascalCaseを使用します。PascalCaseは、単語の先頭を大文字にして連結する形式です。

public class UserAccount{}

public readonly struct Money{}

public record OrderSummary(int OrderId, decimal TotalAmount);

public enum PaymentStatus{Pending,Completed,Failed}

型名には、役割が分かる名詞または名詞句を使用します。DataInfoManagerHelperなど意味が広すぎる単語は、責務が曖昧になりやすいため注意が必要です。

たとえば、UserManagerよりも、実際の責務に応じてUserAuthenticatorUserRegistrationServiceUserRepositoryなどと命名したほうが意図を把握しやすくなります。

3-2. メソッド名・プロパティ名・イベント名の命名ルール

メソッド名とプロパティ名にもPascalCaseを使用します。

メソッド名は、処理内容を表す動詞または動詞句にします。

public User FindUser(int userId){// ...}

public void CancelOrder(int orderId){// ...}

public decimal CalculateTotalPrice(){// ...}

プロパティ名は、保持する値を表す名詞または形容詞にします。

public string DisplayName { get; init; } = string.Empty;

public bool IsActive { get; private set; }

public DateTime CreatedAt { get; init; }

イベント名もPascalCaseを使用し、発生した事実が分かる名前にします。

public event EventHandler? OrderCompleted;

イベント処理を表すメソッドでは、OnOrderCompletedのような名前が一般的です。

protected virtual void OnOrderCompleted(){OrderCompleted?.Invoke(this, EventArgs.Empty);}

3-3. ローカル変数・引数はcamelCaseにする

ローカル変数とメソッド引数にはcamelCaseを使用します。camelCaseは、最初の単語を小文字で始め、以降の単語の先頭を大文字にする形式です。

public decimal CalculateDiscount(decimal totalAmount,int customerRank){decimal discountRate = GetDiscountRate(customerRank);decimal discountedAmount = totalAmount * discountRate;

return discountedAmount;

}

変数名は、型ではなく値の意味を表すことが重要です。

// 意味が分かりにくいstring str;int num;List<User> list;

// 意味が分かりやすいstring customerName;int retryCount;List<User> activeUsers;

短いループ内のiや、座標を表すxyなど、一般的で意味が明確な短縮名は使用できます。ただし、スコープが広い変数には具体的な名前を付けます。

3-4. privateフィールドにアンダースコアを付けるかどうか

privateインスタンスフィールドを_camelCaseで記述する方法は、C#で広く使われています。

public sealed class OrderService{private readonly IOrderRepository _orderRepository;private readonly ILogger<OrderService> _logger;

public OrderService(IOrderRepository orderRepository,ILogger&lt;OrderService&gt; logger){_orderRepository = orderRepository;_logger = logger;}

}

アンダースコアを付ける利点は、ローカル変数や引数とフィールドを見分けやすいことです。特にコンストラクターで依存関係を代入するときに、this.を毎回付けなくても区別できます。

一方、アンダースコアを付けず、フィールド参照時にthis.を使用する規約もあります。

private readonly IOrderRepository orderRepository;

public OrderService(IOrderRepository orderRepository){this.orderRepository = orderRepository;}

どちらを選んでも動作上の違いはありません。プロジェクト内で統一され、自動チェックできることを優先します。

3-5. インターフェイス名はIプレフィックスを使う

C#では、インターフェイス名を大文字のIから始める慣習があります。

public interface IUserRepository{Task<User?> FindByIdAsync(int userId,CancellationToken cancellationToken);}

実装クラスは、インターフェイス名からIを除いた名前にする方法が一般的です。

public sealed class UserRepository : IUserRepository{// ...}

ただし、実装の役割や方式が異なる場合は、より具体的な名前を付けます。

public sealed class SqlUserRepository : IUserRepository{}

public sealed class CachedUserRepository : IUserRepository{}

インターフェイスを作ること自体を目的にしてはいけません。実装の差し替え、外部依存の抽象化、テスト容易性の向上など、必要な理由がある場合に導入します。

3-6. 定数・readonly・staticフィールドの命名方針

C#では、定数をすべて大文字のスネークケースにする規約と、PascalCaseにする規約の両方が見られます。.NETの一般的な命名に寄せる場合は、定数にPascalCaseを使います。

private const int DefaultRetryCount = 3;private const string DateFormat = "yyyy-MM-dd";

privateのreadonlyフィールドは、通常のprivateフィールドと同じ形式にします。

private readonly IUserRepository userRepository;

private staticフィールドを通常のフィールドと区別したい場合は、sプレフィックスを使用する規約もあります。

private static readonly TimeSpan s_defaultTimeout =TimeSpan.FromSeconds(30);

重要なのは、conststatic readonly、インスタンスのreadonlyを適切に使い分けることです。

  • コンパイル時に確定する不変値はconst

  • 実行時に生成する不変値はstatic readonly

  • インスタンスごとに保持する不変の依存関係はreadonly

命名だけでなく、値の性質に合った宣言方法も規約に含めます。

3-7. bool変数・非同期メソッド・コレクション名の付け方

bool値は、真偽を自然に判断できる名前にします。IsHasCanShouldなどから始めると意図が明確になります。

bool isEnabled;bool hasPermission;bool canRetry;bool shouldSendNotification;

flagcheckだけでは、trueが何を意味するのか分かりません。

// 避けたい例bool flag;bool userCheck;

非同期メソッドには、原則としてAsync接尾辞を付けます。

public async Task<User?> FindUserAsync(int userId,CancellationToken cancellationToken){// ...}

コレクションには複数形を使用します。

IReadOnlyList<User> users;Dictionary<int, Product> productsById;IEnumerable<Order> pendingOrders;

辞書の場合は、usersだけでなく、キーの意味が分かるusersByIdpricesByProductCodeのような名前にすると読みやすくなります。

3-8. 避けるべき命名:省略語・ハンガリアン記法・意味の薄い名前

一般に認知されていない省略語は避けます。

// 分かりにくいint usrCnt;DateTime updDt;string custNm;

// 分かりやすいint userCount;DateTime updatedAt;string customerName;

idurlhttpxmldtoなど、チーム内で意味が明確な略語は使用できます。ただし、大文字・小文字の扱いは統一します。

型を名前に含めるハンガリアン記法も、通常は必要ありません。

// 避けたい例string strUserName;int intRetryCount;List<User> lstUsers;

C#ではIDEや型推論によって型を確認できます。型が変わるたびに変数名まで修正する必要がある命名は、保守性を下げます。

また、dataitemvaluetempresultなどの名前は、狭いスコープで意味が明確な場合に限定します。

4. C#の書式ルール:読みやすいコードにするための規約

4-1. インデント・スペース・改行の基本方針

C#では、インデントに4つのスペースを使用する方法が一般的です。タブを使うかスペースを使うかより、プロジェクト内で統一されていることが重要です。

演算子の前後やカンマの後にはスペースを入れます。

int totalPrice = unitPrice * quantity;SendEmail(user.Email, subject, body);

メソッド呼び出しの丸括弧の内側には、通常スペースを入れません。

// 推奨CalculateTotal(price, quantity);

// 避けたい例CalculateTotal( price, quantity );

長い引数や条件式は、無理に1行へ詰め込まず、意味のまとまりごとに改行します。

User? user = await _userRepository.FindByEmailAsync(request.Email,cancellationToken);

1行の最大文字数を厳密に決める場合でも、機械的な改行によって読みやすさを損なわないようにします。目安として上限を定め、例外を認める運用が適しています。

4-2. 波括弧の位置と1行ステートメントの扱い

C#では、波括弧を制御構文やメソッド宣言の次の行に置く形式が一般的です。

if (user.IsActive){SendNotification(user);}

if文の処理が1行でも、波括弧を省略しない規約にすると、後から処理を追加したときのミスを防ぎやすくなります。

// 推奨if (order.IsCanceled){return;}

// 省略すると、修正時に事故が起こりやすいif (order.IsCanceled)return;

自動実装プロパティや単純な式形式メンバーは、1行で記述しても読みやすい場合があります。

public string FullName => $"{LastName} {FirstName}";

一律に1行記述を禁止するのではなく、単純さと可読性を基準にします。

4-3. varを使う場面と明示的な型を書く場面

varは型を省略する機能ではなく、右辺からコンパイラーが型を決定する機能です。静的型付けであることは変わりません。

右辺から型が明確な場合は、varを使うと重複を減らせます。

var user = new User();var users = new List<User>();var orderRepository = new SqlOrderRepository(connection);

一方、メソッドの戻り値から型を判断しにくい場合は、明示的に記述したほうが読みやすいことがあります。

User user = FindUser(userId);decimal totalAmount = CalculateTotal(order);

規約としては、次のいずれかを選びます。

  • 組み込み型を除き、型が明確ならvarを使う

  • 右辺に型名が現れる場合だけvarを使う

  • 匿名型など、明示できない場合に限定する

  • 原則としてvarを使い、可読性が落ちる場合は明示する

重要なのは、varを使うかどうかを個人ごとに判断させないことです。IDEのコードスタイル設定で自動化できる基準にすると、レビューでの議論を減らせます。

4-4. usingの並び順と不要なusingの削除

usingディレクティブは、並び順と配置を統一します。

using System;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using MyApplication.Domain.Users;

System名前空間を先にするか、すべてアルファベット順にするかはチームで決めます。グループ間に空行を入れるかどうかも、自動整形可能な形で統一します。

不要なusingは削除します。不要な記述が残っていると、実際に利用している依存関係を判断しにくくなります。

グローバルusingや暗黙的usingを利用する場合は、どの名前空間が暗黙的に利用可能かをプロジェクト内で共有します。便利だからといって多くの独自名前空間をグローバル化すると、型名の衝突や依存関係の不透明化につながります。

4-5. コメントを書くべき場所と書きすぎを防ぐ基準

コメントは、「コードを読めば分かること」ではなく、「コードだけでは分からない理由」を説明するために書きます。

// 悪い例:処理内容をそのまま説明しているretryCount++;

// 良い例:業務上の理由を説明している// 外部API側の一時的なレート制限を考慮し、最大3回まで再試行する。retryCount++;

複雑なコードをコメントで補う前に、変数名やメソッド分割で意図を表現できないか検討します。

// コメントがないと理解しにくいif (customer.TotalPurchaseAmount >= 100000 &&customer.LastPurchaseAt >= DateTime.Today.AddMonths(-6)){// ...}

条件をメソッドへ抽出すると、コメントを減らせます。

if (IsEligibleForLoyaltyProgram(customer)){// ...}

TODOコメントを使う場合は、担当者名だけでなく、課題番号や対応条件を記載します。放置されるTODOを増やさない運用も必要です。

4-6. XMLドキュメントコメントを使うべき範囲

XMLドキュメントコメントは、公開API、共通ライブラリ、他チームが利用するクラスやメソッドに対して有効です。

/// <summary>/// 指定されたユーザーIDに対応するユーザーを取得します。/// </summary>/// <param name="userId">取得対象のユーザーID。</param>/// <param name="cancellationToken">/// 処理のキャンセルを通知するトークン。/// </param>/// <returns>/// ユーザーが存在する場合はユーザー、存在しない場合はnull。/// </returns>public Task<User?> FindUserAsync(int userId,CancellationToken cancellationToken){// ...}

すべてのprivateメソッドに形式的なXMLコメントを書くと、コード量が増え、内容と実装がずれやすくなります。

規約では、次のように対象を限定すると運用しやすくなります。

  • 外部公開するAPIは必須

  • 共通ライブラリのpublicメンバーは必須

  • 業務上の制約が分かりにくいメソッドは推奨

  • 自明なprivateメンバーには不要

コメントの量ではなく、利用者が誤解しないために必要かどうかを基準にします。

5. C#の設計方針として決めておきたい規約

5-1. クラスの責務を小さく保つルール

クラスは、一つの明確な責務を持つように設計します。ただし、「1クラス1機能」と機械的に細分化するのではなく、変更理由がまとまっているかを確認します。

たとえば、ユーザー登録、メール送信、データベース保存、ログ生成を一つのクラスに詰め込むと、変更の影響範囲が広がります。

public sealed class UserRegistrationService{private readonly IUserRepository _userRepository;private readonly IEmailSender _emailSender;

public UserRegistrationService(IUserRepository userRepository,IEmailSender emailSender){_userRepository = userRepository;_emailSender = emailSender;}public async Task RegisterAsync(RegisterUserRequest request,CancellationToken cancellationToken){var user = User.Create(request.Name, request.Email);await _userRepository.AddAsync(user, cancellationToken);await _emailSender.SendWelcomeEmailAsync(user,cancellationToken);}

}

クラス名から責務を説明できない、依存関係が増え続ける、異なる理由で頻繁に修正される場合は、分割を検討します。

5-2. メソッドの長さ・引数の数・戻り値の方針

メソッドの行数だけを基準に良し悪しを判断するのは適切ではありません。しかし、長いメソッドには複数の責務が含まれている可能性があります。

次の状態が見られたら、メソッド分割を検討します。

  • 処理を説明するために複数のコメントが必要

  • 条件分岐のネストが深い

  • 同じ抽象度ではない処理が混在している

  • 一部だけをテストしにくい

  • 変数の有効範囲が広い

引数が多い場合は、パラメーターオブジェクトを利用します。

public sealed record CreateOrderCommand(int CustomerId,IReadOnlyList<OrderItemRequest> Items,string ShippingAddress,PaymentMethod PaymentMethod);

ただし、引数をまとめるためだけに無関係な値を一つの型へ入れてはいけません。値同士が一つの概念を構成しているかを確認します。

戻り値については、成功・失敗を表す方法を統一します。null、例外、結果型、boolとout引数を場面ごとに無秩序に混在させないことが重要です。

5-3. 例外処理とエラーハンドリングのルール

例外は、呼び出し側で通常の分岐として処理する状態ではなく、処理を継続できない異常を表すために使用します。

例外を捕捉して何もしないコードは禁止すべきです。

// 悪い例try{await SaveAsync();}catch{}

例外を捕捉する場合は、回復、変換、記録、リソース解放などの目的を明確にします。

try{await _paymentGateway.ChargeAsync(payment,cancellationToken);}catch (PaymentGatewayException exception){_logger.LogError(exception,"決済処理に失敗しました。OrderId: {OrderId}",order.Id);

throw new PaymentFailedException(order.Id, exception);

}

throw exception;はスタックトレースを失うため、同じ例外を再スローするときはthrow;を使用します。

catch (Exception exception){_logger.LogError(exception, "処理に失敗しました。");throw;}

ユーザー入力の不正、対象データが存在しない状態、外部サービスの障害などを、どの層でどの形式に変換するかも規約に含めます。

5-4. null許容参照型の扱い方

新規プロジェクトでは、null許容参照型を有効にし、参照型がnullになり得るかを型で表現する方針が有効です。

public User? FindUser(int userId){// 見つからない場合はnull}

nullを許容しないプロパティは、コンストラクターやrequired、初期化子などを利用して確実に初期化します。

public sealed class User{public required string Name { get; init; }

public required string Email { get; init; }

}

null警告を消すためだけに、null免除演算子!を多用してはいけません。

// 警告を隠しているだけUser user = FindUser(userId)!;

nullにならない根拠をコードで表現するか、明示的に検証します。

User user = FindUser(userId)?? throw new UserNotFoundException(userId);

既存プロジェクトへ導入する場合は、プロジェクト全体を一度に変更せず、ディレクトリやファイル単位で段階的に有効化する方法も検討します。

5-5. async/awaitの使い方と非同期処理の規約

I/O待ちを含む処理では、asyncawaitを適切に利用します。

public async Task<Order?> FindOrderAsync(int orderId,CancellationToken cancellationToken){return await _orderRepository.FindByIdAsync(orderId,cancellationToken);}

戻り値を返さない非同期メソッドでも、原則としてasync voidではなくTaskを返します。async voidは、主にイベントハンドラーなど限定された場面で使用します。

キャンセル可能な処理では、CancellationTokenを受け取り、下位の処理へ渡します。

public async Task ProcessOrdersAsync(CancellationToken cancellationToken){IReadOnlyList<Order> orders =await _orderRepository.FindPendingAsync(cancellationToken);

foreach (Order order in orders){cancellationToken.ThrowIfCancellationRequested();await ProcessOrderAsync(order, cancellationToken);}

}

同期処理をTask.Runで囲むだけの疑似的な非同期化は、原則として避けます。また、.Result.Wait()による同期ブロックも、デッドロックやスレッド枯渇の原因になり得るため、非同期処理は呼び出し元まで非同期でつなげます。

5-6. LINQを使う基準と読みやすさを優先する判断

LINQは、絞り込み、変換、集計などを宣言的に記述できる便利な機能です。

IReadOnlyList<User> activeUsers = users.Where(user => user.IsActive).OrderBy(user => user.Name).ToList();

一方、複雑な条件や副作用を含む処理をLINQへ詰め込むと、読みやすさが低下します。

// 避けたい例var results = items.Where(x => Check(x) && UpdateCache(x)).Select(x => Process(x) ? Convert(x) : CreateDefault(x)).ToList();

次のような場合は、foreachのほうが適していることがあります。

  • 途中で処理を打ち切る

  • 複数の副作用がある

  • エラー処理が必要

  • 条件分岐が複雑

  • デバッグ時に処理を追いにくい

短く書けることではなく、処理の意図を自然に読めることを基準にします。

5-7. DTO・Entity・Service・Repositoryなどの役割分担

各種クラスの役割を明文化すると、処理をどこに書くべきか判断しやすくなります。

DTO

レイヤー間や外部とのデータ受け渡しに使用します。原則として複雑な業務ロジックを持たせません。

Entity

識別子とライフサイクルを持つ業務上の概念を表します。自身の状態に関する不変条件や振る舞いを持たせます。

Service

複数のオブジェクトを組み合わせて実現する処理や、特定のEntityだけに所属しない業務処理を担当します。

Repository

永続化の詳細を隠し、Entityの取得や保存を抽象化します。

ControllerやEndpoint

HTTPリクエストの受け取り、入力値の変換、認証結果の利用、レスポンス生成を担当します。業務ロジックを直接書きすぎないようにします。

名前だけを付け分けても、役割が守られていなければ意味がありません。各レイヤーが参照してよい依存先もあわせて定義します。

5-8. テストしやすい設計にするための依存関係のルール

テストしやすいコードでは、時刻、ファイル、データベース、外部API、乱数などの外部依存を分離します。

public interface IClock{DateTimeOffset UtcNow { get; }}
public sealed class SubscriptionService{private readonly IClock _clock;

public SubscriptionService(IClock clock){_clock = clock;}public bool IsExpired(Subscription subscription){return subscription.ExpiresAt &lt;= _clock.UtcNow;}

}

DateTime.Nowや静的な外部アクセスを処理内へ直接埋め込むと、テスト結果を固定しにくくなります。

ただし、すべてのクラスにインターフェイスを作る必要はありません。差し替えたい外部依存や、境界となる処理を中心に抽象化します。

依存関係は、コンストラクターで受け取る方法を基本にします。必要な依存が明確になり、不完全な状態のオブジェクトが生成されにくくなります。

6. チームで守りやすいC#コーディング規約の作り方

6-1. 最初から細かく決めすぎない

最初から数百項目の規約を作ると、内容を覚えられず、運用されないドキュメントになりがちです。

まずは、開発中に頻繁に判断が分かれる項目を優先します。

  • publicメンバーとprivateフィールドの命名

  • varの使用基準

  • null許容参照型の扱い

  • 例外処理

  • 非同期メソッド

  • ログ出力

  • 自動整形

  • コードレビューの必須項目

実際に開発し、同じ議論が繰り返された項目を規約へ追加します。規約は完成品として作るのではなく、チームの経験を蓄積するドキュメントとして育てます。

6-2. 必須ルール・推奨ルール・禁止ルールに分ける

規約には重要度を付けます。

必須ルールの例

  • publicな型とメンバーはPascalCaseにする

  • 非同期メソッドにはAsyncを付ける

  • キャンセル可能なI/O処理ではCancellationTokenを伝播する

  • null許容参照型の警告を放置しない

推奨ルールの例

  • privateフィールドは_camelCaseにする

  • 型が明確な場合はvarを使用する

  • 1メソッドの責務を小さくする

禁止ルールの例

  • 例外を握りつぶさない

  • async voidを通常メソッドで使わない

  • null警告を消す目的だけで!を付けない

  • 意味のない略語やハンガリアン記法を使わない

重要度を明確にすると、レビューで修正必須の指摘と、改善提案を区別できます。

6-3. コードレビューで指摘する基準を明文化する

コードレビューでは、指摘の優先順位を決めておきます。

優先して確認すべきなのは、次の項目です。

  1. 仕様を満たしているか

  2. 不具合やセキュリティ上の問題がないか

  3. 設計や責務が適切か

  4. テストが十分か

  5. 命名や可読性に問題がないか

  6. 書式が規約に合っているか

書式や単純な命名違反は、できる限りAnalyzerやフォーマッターへ任せます。人が確認するレビューでは、設計、仕様、例外ケースなど、自動化しにくい内容へ時間を使います。

レビューコメントには、重要度を示す表現を付ける方法も有効です。

  • must:マージ前に修正が必要

  • should:原則として修正してほしい

  • suggestion:改善案

  • question:意図の確認

  • nit:軽微な指摘

6-4. 規約違反を個人攻撃にしない運用ルール

規約違反は、開発者の能力や人格の問題として扱ってはいけません。規約が分かりにくい、自動化されていない、既存コードが統一されていないといった環境側の問題も考えられます。

レビューでは、「なぜこの書き方をしたのか」と責めるのではなく、規約や設計上の理由を共有します。

このプロジェクトでは、非同期メソッドにAsync接尾辞を付けています。呼び出し側から非同期処理だと判断しやすくするため、GetUserAsyncへの変更をお願いします。

同じ違反が繰り返される場合は、人への注意を増やすのではなく、自動検出できないか検討します。

6-5. 新メンバーが迷わないドキュメントの作り方

規約ドキュメントには、ルールだけでなく理由と例を記載します。

ルール:privateインスタンスフィールドは_camelCaseとする。

理由:ローカル変数やコンストラクター引数と識別しやすくするため。

良い例:private readonly IUserRepository _userRepository;

悪い例:private readonly IUserRepository userRepository;

規約の保存場所は、開発者がすぐに見つけられる場所にします。リポジトリ内のCONTRIBUTING.mddocs/coding-guidelines.mdに置くと、コードと同じ履歴で管理できます。

導入手順として、IDE設定、Analyzerの実行方法、dotnet formatの使い方、CIエラーの直し方も記載します。

6-6. 規約を定期的に見直すタイミング

規約は固定せず、次のタイミングで見直します。

  • .NETやC#のバージョンを更新したとき

  • AnalyzerやIDEの設定を変更したとき

  • 同じレビュー指摘が繰り返されたとき

  • 例外ルールが増えたとき

  • 新しいアーキテクチャを採用したとき

  • 大規模な機能追加やリファクタリングを行うとき

  • チーム構成が大きく変わったとき

使用されていないルールや、自動化できない細かなルールは削除も検討します。規約を増やすことではなく、判断を分かりやすくすることが目的です。

7. C#コーディング規約を自動化する方法

7-1. EditorConfigで書式ルールを統一する

.editorconfigを利用すると、IDEやエディターをまたいで書式やコードスタイルを共有できます。

root = true

[*.cs]charset = utf-8end_of_line = lfinsert_final_newline = trueindent_style = spaceindent_size = 4

dotnet_sort_system_directives_first = truedotnet_separate_import_directive_groups = true

csharp_new_line_before_open_brace = allcsharp_prefer_braces = true:warning

dotnet_naming_rule.private_fields_should_be_camel_case.severity = warningdotnet_naming_rule.private_fields_should_be_camel_case.symbols = private_fieldsdotnet_naming_rule.private_fields_should_be_camel_case.style = underscore_camel_case

dotnet_naming_symbols.private_fields.applicable_kinds = fielddotnet_naming_symbols.private_fields.applicable_accessibilities = private

dotnet_naming_style.underscore_camel_case.required_prefix = _dotnet_naming_style.underscore_camel_case.capitalization = camel_case

設定項目には重要度を指定できます。

  • silent

  • suggestion

  • warning

  • error

最初からすべてをerrorにすると、既存コードで大量のエラーが発生する可能性があります。導入初期は警告として可視化し、対応方針が固まったルールから段階的に厳格化します。

7-2. Visual Studioのコードスタイル設定を活用する

Visual Studioには、命名、var、式形式メンバー、波括弧、名前空間、修飾子などのコードスタイル設定があります。

ただし、各開発者がローカル設定だけを変更すると、チーム内で結果が一致しません。共有すべき設定は.editorconfigへ記述し、リポジトリで管理します。

Visual Studioのコードクリーンアップを設定すると、保存時や任意のタイミングで次の処理を実行できます。

  • コードの整形

  • 不要なusingの削除

  • usingの並べ替え

  • 明示的な型とvarの統一

  • 修飾子の追加

  • コードスタイル違反の修正

IDEの設定手順も規約ドキュメントへ記載すると、新しいメンバーが同じ環境を作りやすくなります。

7-3. Roslyn Analyzerで規約違反を検出する

Roslyn Analyzerは、C#コードを構文や意味のレベルで解析し、問題を警告やエラーとして検出します。

コンパイラー標準の解析に加え、独自のAnalyzerや各種パッケージを導入することで、次のような問題を検出できます。

  • 命名規則違反

  • nullに関する問題

  • 不適切な非同期処理

  • 未使用コード

  • APIの誤用

  • パフォーマンス上の問題

  • セキュリティ上の問題

規約違反をAnalyzerで検出できれば、レビュー担当者による指摘のばらつきを減らせます。

警告を無効化するときは、理由を残します。

#pragma warning disable CAxxxx// 外部仕様との互換性を維持するため、この箇所では例外として許可する。#pragma warning restore CAxxxx

プロジェクト全体で無効化する前に、対象範囲を限定できないか検討します。

7-4. StyleCop Analyzersを導入するメリット

StyleCop Analyzersは、C#の書式、命名、ドキュメント、メンバー配置などをチェックするAnalyzerです。

導入すると、次のようなルールを自動検出できます。

  • 波括弧や空白の書式

  • usingの配置

  • メンバーの並び順

  • XMLドキュメントコメント

  • ファイル名と型名の対応

  • アクセス修飾子の明示

一方、既定設定のすべてがプロジェクトに適しているとは限りません。特にXMLコメントやファイルヘッダーのルールを厳格に適用すると、記述量が増えることがあります。

採用するルールと無効化するルールを決め、設定理由を共有することが重要です。

7-5. dotnet formatで整形を自動化する

dotnet formatを使用すると、ソリューションやプロジェクトに対してコードスタイルと書式を適用できます。

dotnet format

変更せずに違反の有無だけを確認する場合は、検証用オプションを利用します。

dotnet format --verify-no-changes

ローカル開発では整形に使用し、CIでは未整形コードを検出する運用が可能です。

フォーマッターによる大規模な変更は、機能変更と同じコミットに含めないようにします。整形だけの差分を分離すると、レビューや履歴確認が容易になります。

7-6. CIで規約チェックを行う運用例

CIでビルド、テスト、フォーマット、Analyzerを実行すると、規約違反のコードがメインブランチへ入るのを防げます。

基本的な流れは次のとおりです。

dotnet restoredotnet format --verify-no-changesdotnet build --no-restoredotnet test --no-build

重要なAnalyzer警告をビルドエラーとして扱う設定も有効です。ただし、既存プロジェクトで警告が多い場合は、最初からすべてをエラーにしないほうがよいでしょう。

導入時点の警告を基準として記録し、新しく増えた警告だけを禁止する方法や、変更対象のプロジェクトから段階的に厳格化する方法があります。

CIで失敗したときに、開発者がローカルで同じコマンドを実行できるようにすることも重要です。

8. C#コーディング規約の実践例

8-1. 命名規則のサンプル

次のコードは、一般的なC#の命名規則を適用した例です。

public interface IOrderRepository{Task<Order?> FindByIdAsync(int orderId,CancellationToken cancellationToken);}

public sealed class OrderService{private const int DefaultRetryCount = 3;

private readonly IOrderRepository _orderRepository;public OrderService(IOrderRepository orderRepository){_orderRepository = orderRepository;}public async Task&lt;bool&gt; CanCancelOrderAsync(int orderId,CancellationToken cancellationToken){Order? order = await _orderRepository.FindByIdAsync(orderId,cancellationToken);return order is not null &amp;&amp; order.CanCancel;}

}

型と公開メンバーはPascalCase、引数とローカル変数はcamelCase、privateフィールドは_camelCase、非同期メソッドはAsyncで終わっています。

8-2. クラス設計のサンプル

業務処理、永続化、通知を分離した例です。

public sealed class OrderCancellationService{private readonly IOrderRepository _orderRepository;private readonly INotificationSender _notificationSender;

public OrderCancellationService(IOrderRepository orderRepository,INotificationSender notificationSender){_orderRepository = orderRepository;_notificationSender = notificationSender;}public async Task CancelAsync(int orderId,CancellationToken cancellationToken){Order order = await GetOrderAsync(orderId,cancellationToken);order.Cancel();await _orderRepository.SaveAsync(order,cancellationToken);await _notificationSender.SendOrderCanceledAsync(order,cancellationToken);}private async Task&lt;Order&gt; GetOrderAsync(int orderId,CancellationToken cancellationToken){return await _orderRepository.FindByIdAsync(orderId,cancellationToken)?? throw new OrderNotFoundException(orderId);}

}

Serviceはユースケースの進行を担当し、注文をキャンセルできるかどうかの業務判断はOrder自身へ持たせています。

8-3. メソッド分割のサンプル

複数の処理が一つに詰め込まれたコードは、意図を表すメソッドへ分割します。

public async Task ProcessAsync(Order order,CancellationToken cancellationToken){ValidateOrder(order);

decimal totalAmount = CalculateTotalAmount(order);await SaveOrderAsync(order,totalAmount,cancellationToken);await SendConfirmationAsync(order,cancellationToken);

}

分割後は、上位メソッドを読むだけで処理の流れを理解できます。各メソッドを個別にテストしやすくなる点もメリットです。

ただし、1行ごとにメソッドへ分ける必要はありません。処理のまとまりに名前を付けることで、理解しやすくなる場合に分割します。

8-4. コメント・例外処理・非同期処理のサンプル

public async Task<PaymentResult> ExecutePaymentAsync(PaymentRequest request,CancellationToken cancellationToken){ArgumentNullException.ThrowIfNull(request);

try{// 決済事業者の仕様上、同一キーによる再送は重複請求されない。string idempotencyKey =CreateIdempotencyKey(request.OrderId);return await _paymentClient.ChargeAsync(request,idempotencyKey,cancellationToken);}catch (PaymentRejectedException){// 決済拒否は想定される業務結果として呼び出し元へ返す。return PaymentResult.Rejected();}catch (PaymentClientException exception){_logger.LogError(exception,"決済サービスとの通信に失敗しました。OrderId: {OrderId}",request.OrderId);throw new PaymentUnavailableException(request.OrderId,exception);}

}

コメントは外部仕様や判断理由を説明し、例外は想定内の業務結果とシステム障害を区別しています。また、キャンセルトークンを下位処理へ渡しています。

8-5. 良い例と悪い例で見る規約の違い

悪い例では、名前から処理内容を判断できず、例外も握りつぶしています。

public async void Do(int id){try{var x = await repo.Get(id);

    if (x != null){x.F = true;await repo.Save(x);}}catch{}

}

改善例では、メソッド名、変数名、戻り値、キャンセル、例外処理を明確にします。

public async Task ActivateUserAsync(int userId,CancellationToken cancellationToken){User user = await _userRepository.FindByIdAsync(userId,cancellationToken)?? throw new UserNotFoundException(userId);

user.Activate();await _userRepository.SaveAsync(user,cancellationToken);

}

改善のポイントは、コードを長くすることではありません。利用者が推測しなければならない情報を減らすことです。

9. C#コーディング規約テンプレート

9-1. プロジェクト開始時に決める項目一覧

プロジェクト開始時には、最低限次の項目を決めます。

  • 対象となる.NETとC#のバージョン

  • null許容参照型の有効・無効

  • 暗黙的usingの利用方針

  • 名前空間の構成

  • 命名規則

  • varの使用基準

  • 波括弧と改行の形式

  • 例外処理の方針

  • ログ出力の方針

  • 非同期処理の方針

  • CancellationTokenの扱い

  • DTO、Entity、Service、Repositoryの役割

  • テストコードの命名

  • Analyzerとフォーマッター

  • CIでエラーにする項目

  • 生成コードの除外設定

  • レビューで確認する項目

決定内容は、.editorconfig、プロジェクト設定、Analyzer設定、規約ドキュメントへ反映します。

9-2. 命名規則テンプレート

【型】クラス、構造体、レコード、列挙型:PascalCaseインターフェイス:I + PascalCase型パラメーター:TまたはT + PascalCase

【メンバー】publicメソッド:PascalCasepublicプロパティ:PascalCaseイベント:PascalCaseprivateメソッド:PascalCase

【変数】ローカル変数:camelCase引数:camelCaseprivateインスタンスフィールド:_camelCaseprivate staticフィールド:s_camelCase

【その他】定数:PascalCase非同期メソッド:Async接尾辞bool値:Is、Has、Can、Shouldなどで開始コレクション:複数形インターフェイス実装:役割が分かる具体名

略語についてもルールを追加します。

一般的でない省略語は禁止する。型を示すプレフィックスは付けない。ID、URL、HTTPなどの表記方法はプロジェクト内で統一する。

9-3. 書式ルールテンプレート

インデントはスペース4文字とする。タブ文字は使用しない。ファイル末尾に改行を入れる。波括弧は次の行に記述する。if、for、foreach、whileでは波括弧を省略しない。1行に複数のステートメントを書かない。演算子の前後にスペースを入れる。不要なusingは削除する。usingはSystem名前空間を先にし、グループごとに空行を入れる。長い引数は1引数1行を基本とする。書式上の指摘は原則としてフォーマッターで処理する。

9-4. 設計方針テンプレート

クラスには明確な責務を持たせる。業務ルールは可能な限りドメインオブジェクトへ配置する。Controllerに業務ロジックを書かない。外部I/Oは適切な境界で抽象化する。依存関係は原則としてコンストラクターから受け取る。キャンセル可能な非同期処理ではCancellationTokenを受け取る。async voidはイベントハンドラー以外で使用しない。例外を握りつぶさない。通常の分岐に例外を使用しない。null許容性を型で表現する。LINQとforeachは読みやすさを基準に選択する。DTOへ業務ロジックを持たせない。

数値基準を追加する場合は、絶対条件ではなく見直しの目安として扱います。

引数が5個以上になった場合は設計を見直す。依存関係が多いクラスは責務の分割を検討する。ネストが3段階を超える場合は早期returnやメソッド分割を検討する。

9-5. レビュー観点テンプレート

【仕様】要件を満たしているか。異常系と境界値が考慮されているか。

【設計】クラスとメソッドの責務は明確か。依存方向は適切か。不要な抽象化がないか。

【可読性】名前から意図を理解できるか。複雑な条件式が分割されているか。コメントは理由を説明しているか。

【例外・ログ】例外を握りつぶしていないか。同じエラーを複数箇所で重複記録していないか。ログに必要な識別情報が含まれているか。機密情報を出力していないか。

【非同期処理】async voidを不適切に使っていないか。CancellationTokenが伝播されているか。ResultやWaitで同期ブロックしていないか。

【テスト】主要な成功パターンを確認しているか。異常系と境界値を確認しているか。実装詳細に依存しすぎていないか。

9-6. チーム共有用チェックリスト

プルリクエストを作成する前に、開発者自身が次の項目を確認します。

  • ビルドが成功している

  • 自動テストが成功している

  • フォーマッターを実行した

  • Analyzerの警告を確認した

  • 不要なデバッグコードを削除した

  • 不要なコメントやusingを削除した

  • 命名から役割を理解できる

  • nullの扱いを確認した

  • 例外を握りつぶしていない

  • ログへ機密情報を出力していない

  • 非同期処理にキャンセルを伝播している

  • 機能変更と無関係な整形を含めていない

  • 必要なテストを追加または更新した

  • 仕様や規約の変更がある場合はドキュメントを更新した

チェックリストは長くしすぎず、実際に確認される項目へ絞ります。

10. よくある質問

10-1. C#のコーディング規約はMicrosoft公式に従えばよい?

Microsoftや.NETの一般的な規約を基準にする方法は有効です。多くのC#開発者にとって理解しやすく、IDEやAnalyzerの標準設定とも合わせやすいためです。

ただし、公式規約だけでは、プロジェクト固有の設計や運用までは決まりません。DTO、Entity、Serviceの役割、例外処理、ログ、外部API連携、テスト方針などは、チームで追加する必要があります。

公式の慣習を土台にし、必要最小限の独自ルールを追加する形が適しています。

10-2. privateフィールドにアンダースコアは必要?

必須ではありません。_camelCaseを使う方法と、アンダースコアを付けずにthis.で区別する方法のどちらも成立します。

重要なのは、プロジェクト内で統一することです。新規プロジェクトで特別な理由がなければ、C#で広く使われ、引数と区別しやすい_camelCaseを採用すると分かりやすいでしょう。

10-3. varは使わないほうがよい?

varを一律に禁止する必要はありません。右辺から型が明確な場合は、型名の重複を減らし、コードを読みやすくできます。

var users = new List<User>();

一方、戻り値の型を判断しにくい場合は、明示したほうが理解しやすくなります。

PaymentResult result = await ExecutePaymentAsync(request,cancellationToken);

varか明示的な型かではなく、コードを読んだときに型と意図を把握できるかを基準にします。

10-4. 規約違反はすべて修正すべき?

すべてを直ちに修正する必要はありません。規約違反の重要度、修正範囲、既存コードへの影響を考慮します。

不具合やセキュリティ、重大な保守性低下につながる違反は優先して修正します。書式や軽微な命名違反は、自動修正できるタイミングや、対象コードを変更するときに対応する方法もあります。

規約修正だけで大量の差分が発生する場合は、機能変更とは別のプルリクエストに分けます。

10-5. 既存プロジェクトに後から規約を導入するには?

まず、現在のコードを分析し、すでに多く使われている書き方を確認します。そのうえで、優先度の高いルールから導入します。

進め方の一例は次のとおりです。

  1. 現状の命名や書式を調査する

  2. 最低限の規約を合意する

  3. .editorconfigを追加する

  4. Analyzerは警告レベルで導入する

  5. 新規コードや変更部分から適用する

  6. 自動修正できる違反を別コミットで整理する

  7. CIで新しい違反の増加を防ぐ

  8. 段階的に警告をエラーへ変更する

全ファイルを一度に修正しようとすると、レビュー負荷と競合が増えます。継続的に改善できる仕組みを先に作ることが重要です。

10-6. 個人開発でもC#コーディング規約は必要?

個人開発でも、最低限のC#コーディング規約を決める価値があります。数か月後の自分は、現在の実装意図を細部まで覚えているとは限らないためです。

特に、命名規則、書式、null、例外処理、非同期処理を統一すると、将来の修正が容易になります。

個人開発では長い規約書を作る必要はありません。.editorconfigとAnalyzerを設定し、READMEへ設計方針を数項目記載するだけでも効果があります。

まとめ

C#コーディング規約は、コードの見た目をそろえるだけのルールではありません。開発者が同じ基準でコードを書き、読み、レビューし、長期間保守するための共通基盤です。

規約を作るときは、一般的なC#や.NETの慣習を土台にし、プロジェクトで必要なルールだけを追加します。命名規則ではPascalCaseやcamelCase、非同期メソッドのAsync接尾辞などを統一し、書式ルールは.editorconfigやフォーマッターで自動化します。

設計面では、クラスの責務、例外処理、null許容参照型、asyncawait、LINQ、依存関係の扱いを明文化することが重要です。ただし、すべてを厳格な禁止事項にするのではなく、必須、推奨、禁止に分けて運用します。

守りやすい規約にするためには、人の注意力に頼らない仕組みが欠かせません。Visual Studioのコードスタイル設定、Roslyn Analyzer、StyleCop Analyzers、dotnet format、CIを組み合わせ、機械が確認できる部分は自動化します。

最も重要なのは、規約の項目数ではありません。チーム内の迷いや不要な議論を減らし、コードの本質的な品質へ集中できる状態を作ることです。