mcpField
M
modern-csharp-coding-standards

modern-csharp-coding-standards

github.com/aaronontheweb開発1.2k2.3k 回インストール今日更新
リポジトリ

modern-csharp-coding-standards について

Modern C# Coding Standardsは、C#の新規作成やリファクタリング、API設計に取り組む開発者向けのスキルです。現代的な言語パターン、型安全性、非同期処理、性能、コンポジション、エラー処理を扱います。

主な機能

  1. 01不変レコードと値オブジェクト
  2. 02Nullable参照型とパターンマッチング
  3. 03キャンセル対応の非同期API
  4. 04SpanとMemoryによる性能指針
  5. 05コンポジションとResultによるエラー処理

活用シーン

  1. 01新しいC#コードで規約を適用
  2. 02既存のC#アプリケーションをリファクタリング
  3. 03ライブラリやサービスの公開APIを設計
  4. 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.md
composition-and-error-handling.md
performance-and-api-design.md
value-objects-and-patterns.md

詳細

モダンな C# コーディング規約

このスキルを使用する場面

以下の場合にこのスキルを使用してください。

  • 新しい C# コードを記述するとき、または既存のコードをリファクタリングするとき
  • ライブラリやサービスの公開 API を設計するとき
  • パフォーマンスが重要なコードパスを最適化するとき
  • 強い型付けを備えたドメインモデルを実装するとき
  • async/await を多用するアプリケーションを構築するとき
  • バイナリデータ、バッファー、または高スループットのシナリオを扱うとき

参考ファイル

基本原則

  1. デフォルトで不変にする - record 型と init 専用プロパティを使用します
  2. 型安全性 - null 許容参照型と値オブジェクトを活用します
  3. モダンなパターンマッチング - switch 式とパターンを積極的に使用します
  4. あらゆる場面で非同期処理を使う - 適切なキャンセルをサポートする非同期 API を優先します
  5. 割り当てゼロのパターン - パフォーマンスが重要なコードでは Span<T> と Memory<T> を使用します
  6. API 設計 - 抽象型を受け取り、適切に具体的な型を返します
  7. 継承よりコンポジションを優先する - 抽象基底クラスを避け、コンポジションを優先します
  8. 値オブジェクトには構造体を使う - 値オブジェクトには 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 を参照してください。


追加リソース

コメント