modern-csharp-coding-standards について
Modern C# Coding Standardsは、C#の新規作成やリファクタリング、API設計に取り組む開発者向けのスキルです。現代的な言語パターン、型安全性、非同期処理、性能、コンポジション、エラー処理を扱います。
主な機能
- 01不変レコードと値オブジェクト
- 02Nullable参照型とパターンマッチング
- 03キャンセル対応の非同期API
- 04SpanとMemoryによる性能指針
- 05コンポジションとResultによるエラー処理
活用シーン
- 01新しいC#コードで規約を適用
- 02既存のC#アプリケーションをリファクタリング
- 03ライブラリやサービスの公開APIを設計
- 04高スループットまたは非同期コードを改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aaronontheweb/ dotnet-skills --skill modern-csharp-coding-standardsインストール後、Claude Code で/modern-csharp-coding-standardsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
anti-patterns-and-reflection.mdcomposition-and-error-handling.mdperformance-and-api-design.mdvalue-objects-and-patterns.md詳細
モダンな C# コーディング規約
このスキルを使用する場面
以下の場合にこのスキルを使用してください。
- 新しい C# コードを記述するとき、または既存のコードをリファクタリングするとき
- ライブラリやサービスの公開 API を設計するとき
- パフォーマンスが重要なコードパスを最適化するとき
- 強い型付けを備えたドメインモデルを実装するとき
- async/await を多用するアプリケーションを構築するとき
- バイナリデータ、バッファー、または高スループットのシナリオを扱うとき
参考ファイル
- value-objects-and-patterns.md:値オブジェクトの完全な例とパターンマッチングのコード
- performance-and-api-design.md:Span/Memory の例と API 設計の原則
- composition-and-error-handling.md:継承よりコンポジションを優先する方法、Result 型、テストのパターン
- anti-patterns-and-reflection.md:リフレクションの回避と一般的なアンチパターン
基本原則
- デフォルトで不変にする -
record型とinit専用プロパティを使用します - 型安全性 - null 許容参照型と値オブジェクトを活用します
- モダンなパターンマッチング -
switch式とパターンを積極的に使用します - あらゆる場面で非同期処理を使う - 適切なキャンセルをサポートする非同期 API を優先します
- 割り当てゼロのパターン - パフォーマンスが重要なコードでは
Span<T>とMemory<T>を使用します - API 設計 - 抽象型を受け取り、適切に具体的な型を返します
- 継承よりコンポジションを優先する - 抽象基底クラスを避け、コンポジションを優先します
- 値オブジェクトには構造体を使う - 値オブジェクトには
readonly record structを使用します
言語パターン
不変データには Record を使用する(C# 9+)
DTO、メッセージ、イベント、ドメインエンティティには record 型を使用します。
// Simple immutable DTO
public record CustomerDto(string Id, string Name, string Email);
// Record with validation in constructor
public record EmailAddress
{
public string Value { get; init; }
public EmailAddress(string value)
{
if (string.IsNullOrWhiteSpace(value) || !value.Contains('@'))
throw new ArgumentException("Invalid email address", nameof(value));
Value = value;
}
}
// Records with collections - use IReadOnlyList
public record ShoppingCart(
string CartId,
string CustomerId,
IReadOnlyList<CartItem> Items
)
{
public decimal Total => Items.Sum(item => item.Price * item.Quantity);
}
record class と record struct の使い分け:
record class(デフォルト):参照型。エンティティ、集約、複数のプロパティを持つ DTO に使用しますrecord struct:値型。値オブジェクトに使用します(次のセクションを参照)
値オブジェクトには readonly record struct を使用する
パフォーマンスと値セマンティクスのため、値オブジェクトには必ず readonly record struct を使用してください。明示的な変換を使用し、暗黙的な演算子は使用しないでください。
public readonly record struct OrderId(string Value)
{
public OrderId(string value) : this(
!string.IsNullOrWhiteSpace(value)
? value
: throw new ArgumentException("OrderId cannot be empty", nameof(value)))
{ }
public override string ToString() => Value;
}
public readonly record struct Money(decimal Amount, string Currency);
public readonly record struct CustomerId(Guid Value)
{
public static CustomerId New() => new(Guid.NewGuid());
}
複数の値を持つオブジェクト、ファクトリーパターン、暗黙的な変換を行わないルールなど、完全な例については value-objects-and-patterns.md を参照してください。
パターンマッチング(C# 8-12)
コードをより簡潔にするため、switch 式、プロパティパターン、関係パターン、リストパターンを使用します。
public decimal CalculateDiscount(Order order) => order switch
{
{ Total: > 1000m } => order.Total * 0.15m,
{ Total: > 500m } => order.Total * 0.10m,
{ Total: > 100m } => order.Total * 0.05m,
_ => 0m
};
パターンマッチングの完全な例については value-objects-and-patterns.md を参照してください。
null 許容参照型(C# 8+)
プロジェクトで null 許容参照型を有効にし、null を明示的に処理します。
// In .csproj
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Explicit nullability
public string? FindUserName(string userId)
{
var user = _repository.Find(userId);
return user?.Name;
}
// Pattern matching with null checks
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
// Guard clauses with ArgumentNullException.ThrowIfNull (C# 11+)
public void ProcessOrder(Order? order)
{
ArgumentNullException.ThrowIfNull(order);
// order is now non-nullable in this scope
Console.WriteLine(order.Id);
}
継承よりコンポジションを優先する
抽象基底クラスは避けてください。 インターフェイスとコンポジションを使用します。共通ロジックには静的ヘルパーを使用します。バリエーションにはファクトリーメソッドを備えた record を使用します。
完全な例については composition-and-error-handling.md を参照してください。
パフォーマンスパターン
Async/Await のベストプラクティス
// Async all the way - always accept CancellationToken
public async Task<Order> GetOrderAsync(string orderId, CancellationToken cancellationToken)
{
var order = await _repository.GetAsync(orderId, cancellationToken);
return order;
}
// ValueTask for frequently-called, often-synchronous methods
public ValueTask<Order?> GetCachedOrderAsync(string orderId, CancellationToken cancellationToken)
{
if (_cache.TryGetValue(orderId, out var order))
return ValueTask.FromResult<Order?>(order);
return GetFromDatabaseAsync(orderId, cancellationToken);
}
// IAsyncEnumerable for streaming
public async IAsyncEnumerable<Order> StreamOrdersAsync(
string customerId,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var order in _repository.StreamAllAsync(cancellationToken))
{
if (order.CustomerId == customerId)
yield return order;
}
}
主なルール:
- 常に
CancellationTokenを= defaultとともに受け取ります - ライブラリコードでは
ConfigureAwait(false)を使用します - 非同期コードをブロックして待機しないでください(
.Resultや.Wait()は使用しません) - タイムアウトにはリンクされた CancellationTokenSource を使用します
Span と Memory
同期的な割り当てゼロの操作には Span<T>、非同期処理には Memory<T>、大きな一時バッファーには ArrayPool<T> を使用します。
Span/Memory の完全な例と API 設計のセクションについては performance-and-api-design.md を参照してください。
エラー処理:Result 型
想定されるエラーには、汎用の Result<T> ではなく、ドメイン固有の結果型を使用します。例外は想定外のエラーやシステムエラーに限って使用します。
Result 型の完全な実装と使用例については composition-and-error-handling.md を参照してください。
リフレクションベースのメタプログラミングを避ける
禁止: AutoMapper、Mapster、ExpressMapper。代わりに明示的なマッピング拡張メソッドを使用してください。プライベートメンバーへのアクセスが本当に必要な場合は、UnsafeAccessorAttribute(.NET 8 以降)を使用してください。
詳しくは anti-patterns-and-reflection.md を参照してください。
コードの構成
// File: Domain/Orders/Order.cs
namespace MyApp.Domain.Orders;
// 1. Primary domain type
public record Order(
OrderId Id,
CustomerId CustomerId,
Money Total,
OrderStatus Status,
IReadOnlyList<OrderItem> Items
)
{
public bool IsCompleted => Status is OrderStatus.Completed;
public Result<Order, OrderError> AddItem(OrderItem item)
{
if (Status is not OrderStatus.Draft)
return Result<Order, OrderError>.Failure(
new OrderError("ORDER_NOT_DRAFT", "Can only add items to draft orders"));
var newItems = Items.Append(item).ToList();
var newTotal = new Money(
Items.Sum(i => i.Total.Amount) + item.Total.Amount,
Total.Currency);
return Result<Order, OrderError>.Success(
this with { Items = newItems, Total = newTotal });
}
}
// 2. Enums for state
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. Related types
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. Value objects
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. Errors
public readonly record struct OrderError(string Code, string Message);
ベストプラクティスのまとめ
推奨事項
- DTO、メッセージ、ドメインエンティティには
recordを使用する - 値オブジェクトには
readonly record structを使用する switch式を使ったパターンマッチングを活用する- nullable 参照型を有効にし、その警告を尊重する
- すべての I/O 操作で async/await を使用する
- すべての非同期メソッドで
CancellationTokenを受け取る - 高パフォーマンスが求められる場面では
Span<T>とMemory<T>を使用する - 抽象型(
IEnumerable<T>、IReadOnlyList<T>)を受け取る - 想定されるエラーには
Result<T, TError>を使用する - 大きな割り当てには
ArrayPool<T>でバッファーをプールする - 継承よりコンポジションを優先する
禁止事項
recordで対応できる場合に、可変クラスを使用しない- 値オブジェクトにクラスを使用しない(
readonly record structを使用する) - 深い継承階層を作成しない
- nullable 参照型の警告を無視しない
- 非同期コードをブロックしない(
.Result、.Wait()) Span<byte>で十分な場合にbyte[]を使用しないCancellationTokenパラメーターを忘れない- API から可変コレクションを返さない
- 想定されるビジネスエラーに例外をスローしない
- 大きな配列を繰り返し割り当てない(
ArrayPoolを使用する)
アンチパターンの詳しい例については、anti-patterns-and-reflection.md を参照してください。
追加リソース
- C# 言語仕様:https://learn.microsoft.com/en-us/dotnet/csharp/
- パターンマッチング:https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching
- Span と Memory:https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/
- 非同期処理のベストプラクティス:https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
- .NET のパフォーマンスに関するヒント:https://learn.microsoft.com/en-us/dotnet/framework/performance/