一键导入
dotnet-modern-csharp-coding-standards
モダン C#(12+)で record、パターンマッチング、合成、Result 型エラーハンドリングを使った 慣用的で高性能なコードを書く。新規 C# コードの作成、API 設計、 または C# 12+ イディオムへのリファクタリング。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
モダン C#(12+)で record、パターンマッチング、合成、Result 型エラーハンドリングを使った 慣用的で高性能なコードを書く。新規 C# コードの作成、API 設計、 または C# 12+ イディオムへのリファクタリング。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
こんなときに使う: Ubuntu / Linux サーバーに SSH で接続し、sudo、systemd サービス、HTTP 監視を一連で安全に進めたいとき。 接続前に SSH_AUTH_SOCK を含む認証状態を固定し、認証で止まらずに サーバー接続・権限確認・サービス起動・停止・再起動・状態確認を一気に行いたいとき。
調査→修正→検証→ふりかえり/後続 Issue 化までを 1 つの改善ループで回したいときに使う。 実装前の再現確認や、review 指摘・検証結果をもとに次のアクションへつなぐ。
こんなときに使う: 現在の会話内容をもとに、実装・エージェント発注に直結する PRD を作りたいとき。 追加のインタビューはせず、すでに会話に出ている内容だけから構成する。 情報が不足している場合は捏造せず「未確定」として明示する。
こんなときに使う: ユーザーが「インタビューして」「質問して」「設計を詰めたい」などと言ったら使う。 計画や設計の重要な判断軸を洗い出し、具体例・反例・影響範囲まで深掘りして要件整理に落とす。
Copilot の custom skill / agent / repository instructions の作成・改善・構造確認を 1 つの入口にまとめる。複合スキルとして、対象に応じて適切な authoring ルートへ 分けつつ、実行時のモデル呼び出しを抑止してルーティングを優先する。試作から `plugins/*` 配布へ昇格するときの name / description 整備も扱う。skill / agent / repo-wide instructions / path-specific instructions を新規作成したいとき、既存定義を育てたいとき、公開前に責務や導線を確かめたいとき。
新しい custom agent を既存 agent 群と同じ型で立ち上げる。agent の新規追加、役割分離のための専門 agent 作成、既存群の隙間を埋めたいとき。
| name | dotnet-modern-csharp-coding-standards |
| description | モダン C#(12+)で record、パターンマッチング、合成、Result 型エラーハンドリングを使った 慣用的で高性能なコードを書く。新規 C# コードの作成、API 設計、 または C# 12+ イディオムへのリファクタリング。 |
モダン C#(12+)で慣用的なコードを書くための簡潔なガードレール。record によるデータモデリング、パターンマッチング、合成優先の設計、Railway 指向のエラーハンドリングをカバーします。.NET 8+ と C# 12+ を前提にし、外部依存は使いません。 ゴール駆動で使うため、最初に達成したいゴール、成功条件、確認手段を短く固定します。
略語: DTO (Data Transfer Object), API (Application Programming Interface), DI (Dependency Injection), BCL (Base Class Library), GC (Garbage Collector).
record、record struct、class、struct の使い分け判断Result<T, TError> でエラーハンドリング| Skill | Scope |
|---|---|
dotnet-type-design-performance | Span<T>、Memory<T>、ArrayPool、ゼロアロケーションパターン |
dotnet | API 設計や新規基盤 skill の入口 |
dotnet-csharp-concurrency-patterns | async/await ベストプラクティス、CancellationToken、IAsyncEnumerable |
record 型と init 専用プロパティを使用。可変状態は並行処理バグとロジックバグの最大の原因。readonly record struct による値オブジェクト、強く型付けされた ID を活用し、コンパイル時にエラーを検出。if/else チェーンを switch 式とリレーショナル/プロパティ/リストパターンに置き換え、表現力豊かで網羅的な分岐を実現。Result<T, TError> を使用。例外は本当に予期しない障害にのみ。Values: 基礎と型の追求(最小形式で最大可能性を生む設計思想), 温故知新(C# の進化を活かしつつ堅実な原則を守る)
DTO、メッセージ、ドメインエンティティには record を使用。値オブジェクトには readonly record struct を使用。
// 不変 DTO
public record CustomerDto(string Id, string Name, string Email);
// 値オブジェクト — 常に readonly record struct
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
public override string ToString() => Value.ToString();
}
// バリデーション付き値オブジェクト
public readonly record struct Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
if (amount < 0)
throw new ArgumentException("Amount cannot be negative");
if (currency is not { Length: 3 })
throw new ArgumentException("Currency must be 3-letter code");
Amount = amount;
Currency = currency.ToUpperInvariant();
}
}
使い分けガイド:
| 型 | 使用場面 |
|---|---|
record class | エンティティ、DTO、複数プロパティを持つ集約 |
readonly record struct | 値オブジェクト、強く型付けされた ID、小さな不変値 |
class | 可変サービス、フレームワーク要求の基底クラス |
struct | パフォーマンスクリティカル、小データ(≤16 バイト)、ID なし |
⚠️ 値オブジェクトに暗黙的変換は禁止 — コンパイル時の型安全性を無効化します。詳細は language-patterns.md を参照。
Values: 基礎と型の追求(型で不変条件を守り、コンパイラを味方にする)
分岐ロジックには switch 式を使用。プロジェクト全体で <Nullable>enable</Nullable> を有効化。
// プロパティパターンによる 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
};
// リレーショナル + logical patterns
public string ClassifyTemperature(int temp) => temp switch
{
< 0 => "Freezing",
>= 0 and < 20 => "Cool",
>= 20 and < 30 => "Warm",
>= 30 => "Hot"
};
// null 安全なパターン
public decimal GetDiscount(Customer? customer) => customer switch
{
null => 0m,
{ IsVip: true } => 0.20m,
{ OrderCount: > 10 } => 0.10m,
_ => 0.05m
};
リストパターン、タプルパターン、nullable 処理の詳細は language-patterns.md を参照。
Values: 成長の複利(パターンマッチングの習得が、あらゆる分岐ロジックの品質を底上げする)
// ❌ 抽象基底クラス階層
public abstract class PaymentProcessor
{
public abstract Task<PaymentResult> ProcessAsync(Money amount);
protected async Task<bool> ValidateAsync(Money amount) { /* ... */ }
}
// ✅ インターフェースによる合成
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessAsync(Money amount, CancellationToken ct);
}
public sealed class CreditCardProcessor(
IPaymentValidator validator,
ICreditCardGateway gateway) : IPaymentProcessor
{
public async Task<PaymentResult> ProcessAsync(Money amount, CancellationToken ct)
{
var validation = await validator.ValidateAsync(amount, ct);
if (!validation.IsValid)
return PaymentResult.Failed(validation.Error);
return await gateway.ChargeAsync(amount, ct);
}
}
継承が許容される場面:
ControllerBase)Exception からのカスタム例外)Values: 余白の設計(合成可能な小さな部品が、将来の変化に対応する余白を生む)
予期されるエラーには Result<T, TError> を使用。予期しない障害には例外を使用。
// エラー型
public readonly record struct OrderError(string Code, string Message);
// Result を返すサービス
public async Task<Result<Order, OrderError>> CreateOrderAsync(
CreateOrderRequest request, CancellationToken ct)
{
var validation = ValidateRequest(request);
if (validation.IsFailure)
return Result<Order, OrderError>.Failure(validation.Error);
var order = new Order(OrderId.New(), new CustomerId(request.CustomerId), request.Items);
await _repository.SaveAsync(order, ct);
return Result<Order, OrderError>.Success(order);
}
// Result のパターンマッチング
return result.Match(
onSuccess: order => new OkObjectResult(order),
onFailure: error => error.Code switch
{
"VALIDATION_ERROR" => new BadRequestObjectResult(error.Message),
"NOT_FOUND" => new NotFoundObjectResult(error.Message),
_ => new ObjectResult(error.Message) { StatusCode = 500 }
});
| 状況 | 使用するもの |
|---|---|
| バリデーション失敗、ビジネスルール違反、見つからない | Result<T, TError> |
| ネットワーク障害、null 参照、OOM、プログラミングバグ | Exception |
完全な Result<T, TError> 実装と Railway 合成は error-handling-patterns.md を参照。
Values: ニュートラルな視点(例外と Result を状況に応じて使い分け、偏りのない設計を保つ)
一貫した名前空間とファイルレイアウトに従います:
Domain/
Orders/
Order.cs # 主要ドメイン型 + 関連 record
OrderService.cs # ドメインロジック
IOrderRepository.cs
型ファイル内の順序:
namespace MyApp.Domain.Orders;
// 1. 主要型
public record Order(OrderId Id, CustomerId CustomerId, OrderStatus Status, Money Total, IReadOnlyList<OrderItem> Items)
{
public bool IsCompleted => Status is OrderStatus.Completed;
}
// 2. Enum
public enum OrderStatus { Draft, Submitted, Processing, Completed, Cancelled }
// 3. 関連 record
public record OrderItem(ProductId ProductId, Quantity Quantity, Money UnitPrice)
{
public Money Total => new(UnitPrice.Amount * Quantity.Value, UnitPrice.Currency);
}
// 4. 値オブジェクト
public readonly record struct OrderId(Guid Value)
{
public static OrderId New() => new(Guid.NewGuid());
}
// 5. エラー
public readonly record struct OrderError(string Code, string Message);
Values: 継続は力(一貫したファイル構成が、日々のコードリーディングを高速化する)
record を使用readonly record struct を使用if/else より switch 式によるパターンマッチングを活用<Nullable>enable</Nullable>)CancellationToken を受け取るList<T> ではなく IReadOnlyList<T> を返すResult<T, TError> を使用.Result や .Wait() の呼び出しはデッドロックを引き起こす。最後まで async を貫く。record の代わりに { get; set; } を持つ class を使用。意図しない変更につながる。implicit operator はコンパイル時の型安全性を無効化する。Entity → AggregateRoot → Order → CustomerOrder。フラットな合成を使用する。Result<T, TError> ではなく例外を使用。// ❌ BAD
public class CustomerDto { public string Id { get; set; } public string Name { get; set; } }
// ✅ GOOD
public record CustomerDto(string Id, string Name);
// ❌ BAD — ヒープ割り当て、参照等価
public class OrderId { public string Value { get; } }
// ✅ GOOD — スタック割り当て、値等価
public readonly record struct OrderId(string Value);
// ❌ BAD
public abstract class Entity { }
public abstract class AggregateRoot : Entity { }
public class CustomerOrder : AggregateRoot { }
// ✅ GOOD
public interface IEntity { Guid Id { get; } }
public record Order(OrderId Id, CustomerId CustomerId) : IEntity { Guid IEntity.Id => Id.Value; }
// ❌ BAD — ランタイムエラー、隠れたマッピング
var dto = _mapper.Map<UserDto>(entity);
// ✅ GOOD — コンパイル時チェック、デバッグ可能
public static UserDto ToDto(this UserEntity e) => new(e.Id.ToString(), e.FullName, e.EmailAddress);
詳細はソースジェネレータと UnsafeAccessor について anti-reflection-patterns.md を参照。
// ❌ BAD — 内部リストを公開
public List<Order> GetOrders() => _orders;
// ✅ GOOD — 読み取り専用ビュー
public IReadOnlyList<Order> GetOrders() => _orders;
// ❌ BAD — デッドロックの危険
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;
// ✅ GOOD — 最後まで async
public async Task<Order> GetOrderAsync(OrderId id, CancellationToken ct = default)
=> await _repository.GetAsync(id, ct);
| 必要なもの | 型 | 理由 |
|---|---|---|
| DTO / メッセージ / イベント | record | 不変、値等価、with サポート |
| ドメインエンティティ | record | 同上 + 計算プロパティ |
| 値オブジェクト / 型付き ID | readonly record struct | スタック割り当て、値セマンティクス |
| DI 付き可変サービス | class(sealed) | 可変状態 / ライフサイクルが必要 |
| 小さな数学データ(≤16 バイト) | struct | パフォーマンスクリティカル、ID なし |
| 状況 | 仕組み | 理由 |
|---|---|---|
| バリデーション失敗 | Result<T, TError> | 予期される、呼び出し元が処理すべき |
| ビジネスルール違反 | Result<T, TError> | 通常のフローの一部 |
| エンティティが見つからない | Result<T, TError> | 予期されるクエリ結果 |
| ネットワーク / I/O 障害 | Exception | 予期しないインフラエラー |
| null 参照 / OOM | Exception | プログラミングバグ / システムエラー |
1. 主要ドメイン型(record/class)
2. Enum
3. 関連 record
4. 値オブジェクト(readonly record struct)
5. エラー型