mcpField
M
modern-csharp

modern-csharp

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

modern-csharp について

C#を使う開発者向けに、C# 14と.NET 10の言語機能をまとめたリファレンススキルです。可読性、値型、不変性の指針と、よくあるアンチパターンを扱います。

主な機能

  1. 01C# 14の言語機能を解説
  2. 02コレクション式とパターンマッチングを紹介
  3. 03fieldキーワードと拡張メンバーを説明
  4. 04値型と不変性を重視
  5. 05アンチパターンと代替案を提示

活用シーン

  1. 01C#プロジェクトのコーディング規約を更新
  2. 02DTOや値オブジェクトの型を選択
  3. 03文字列スライス時の割り当てを削減
  4. 04プロパティ検証と遅延初期化を改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill modern-csharp

インストール後、Claude Code で/modern-csharpを入力して読み込まれたか確認します。

詳細

モダンな C#(C# 14 / .NET 10)

基本原則

  1. 最新の安定機能を使う — 目標は C# 14 です。ライブラリで代替するより、言語機能を優先します。
  2. 巧妙さより読みやすさを優先する — パターンマッチングや式形式のメンバーは、適切に使えば読みやすさを向上させますが、深くネストしたパターンはそうではありません。
  3. 可能な限り値型を使う — GC の負荷を軽減するため、record struct、Span<T>、スタック割り当てを優先します。
  4. デフォルトで不変にする — record、readonly、init、required を使い、不正な状態を表現できないようにします。

パターン

よく使われる機能のクイックリファレンス
機能用途例
プライマリコンストラクターDI 注入、フィールドへの代入の削減public class OrderService(IOrderRepo repo, TimeProvider clock) { }
コレクション式すべてのコレクション型で [] を使用 + スプレッドList<string> names = ["Alice", "Bob"]; / int[] all = [..a, ..b, 99];
レコードDTO、値オブジェクト、不変データpublic record CreateOrderRequest(string CustomerId, List<OrderItem> Items);
readonly record structスタックに割り当てる小さな値型public readonly record struct Money(decimal Amount, string Currency);
パターンマッチングswitch 式、リスト/プロパティパターンorder switch { { Total: > 1000 } => "Premium", _ => "Standard" };
リストパターン配列/リストの分解items switch { [] => "Empty", [var x] => $"One: {x}", [var f, .., var l] => $"{f}..{l}" };
Span<T>割り当てなしのスライスReadOnlySpan<char> trimmed = input.Trim(); int.TryParse(trimmed[4..], out id);
生文字列リテラル複数行の SQL、JSON、XMLvar sql = """ SELECT ... """; / 補間あり:$$""" {"id": "{{id}}"} """;
required メンバー初期化を必須にするpublic required string ConnectionString { get; init; }
is パターン + 抽出null/型/プロパティのチェックif (result is { IsSuccess: true, Value: var order }) { ... }
field キーワード(C# 14)

手動で宣言せずに、プロパティアクセサー内から自動生成されたバッキングフィールドにアクセスします。

// GOOD — field keyword for validation in auto-property
public class Product
{
    public string Name
    {
        get => field;
        set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
    }

    public decimal Price
    {
        get => field;
        set => field = value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(value));
    }
}
field による遅延初期化
public class ProductCatalog
{
    // Lazy-load on first access — no manual Lazy<T> or backing field
    public IReadOnlyList<Product> Products
    {
        get => field ??= LoadProducts();
    }

    private static List<Product> LoadProducts() => /* expensive load */;
}
field による変更通知
// INotifyPropertyChanged without manual backing fields
public class OrderViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public string CustomerName
    {
        get => field;
        set
        {
            if (field == value) return;
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomerName)));
        }
    } = "";

    public decimal Total
    {
        get => field;
        set
        {
            if (field == value) return;
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Total)));
        }
    }
}
拡張メンバー(C# 14)

C# 14 では、静的クラス内で extension ブロックを使えるようになりました。従来の拡張メソッドとは異なり、拡張 プロパティ と 静的 拡張メンバーに対応しており、レシーバーはブロック全体に対して一度だけ宣言します。

// GOOD — extension block (shipped C# 14 syntax)
public static class OrderExtensions
{
    extension(Order order)
    {
        public decimal TotalWithTax => order.Total * 1.2m;

        public bool IsHighValue => order.Total > 1000m;

        public string ToSummary() =>
            $"Order #{order.Id}: {order.Total:C} ({order.Items.Count} items)";
    }

    // Static extension members use the type (no receiver instance)
    extension(Order)
    {
        public static Order Empty => Order.Create("none", [], DateTimeOffset.MinValue);
    }
}

// Callers see them as if declared on Order
if (order.IsHighValue) { /* ... */ }

従来の this パラメーターを使った拡張メソッドも引き続き使用でき、併用できます。プロパティや同じレシーバーに対する複数のメンバーが必要な場合は、拡張ブロックを使います。

アンチパターン

モダンな代替手段がある場合は、古いパターンを使わない
// BAD — manual backing field when field keyword works
private string _name;
public string Name
{
    get => _name;
    set => _name = value ?? throw new ArgumentNullException();
}

// BAD — old-style collection initialization
var list = new List<int>() { 1, 2, 3 };

// BAD — Tuple instead of record for domain types
(string Name, decimal Price) product = ("Widget", 9.99m);
// GOOD — record
public record Product(string Name, decimal Price);
パターンマッチングを過度に使わない
// BAD — deeply nested pattern that's hard to read
if (order is { Customer: { Address: { Country: { Code: "US" } } } })

// GOOD — extract to a clear method or use sequential checks
if (order.Customer.Address.Country.Code == "US")
型が明らかでない場合は var を使わない
// BAD — what type is this?
var result = Process(order);

// GOOD — explicit type when not obvious
Result<Order> result = Process(order);
// Also GOOD — var is fine when type is apparent
var orders = new List<Order>();

判断ガイド

シナリオ推奨
DTO/API コントラクトrecord(参照型)
小さな値オブジェクト(2~3 フィールド)readonly record struct
DI を使うサービスプライマリコンストラクター
コレクションの作成コレクション式 []
検証を伴うプロパティfield キーワード
複数行文字列(SQL、JSON)生文字列リテラル """
文字列/配列のスライスSpan<T>
型チェック + 抽出is/switch によるパターンマッチング
初期化の強制required 修飾子
外部型へのメソッド追加拡張メンバー

コメント