csharp について
csharpは、C# 12 / .NET 8+のコードを作成・変更する際のチーム規約です。言語機能、非同期処理とキャンセル、データアクセス、依存性注入、設定を扱います。
主な機能
- 01nullable警告をエラー扱い
- 02C# 12の言語規約
- 03想定内の失敗にResultパターン
- 04キャンセルトークン付き非同期メソッド
- 05EF Coreのクエリと更新の実践
- 06検証済みオプションとHTTPクライアントのパターン
活用シーン
- 01新しいC#コードへの規約適用
- 02.NET 8+プロジェクトの変更レビュー
- 03EF Coreクエリ実装の指針
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alexander-danilenko/ cortex-ai-skills --skill csharpインストール後、Claude Code で/csharpを入力して読み込まれたか確認します。
詳細
C# の規約
C# 12 / .NET 8 以降におけるプロジェクト内の規約です。新規作成または変更するコードに適用してください。指示がない限り、変更していないファイルまで書き換えて規約に合わせる必要はありません。
規約
-
<Nullable>enable</Nullable>を設定し、警告をエラーとして扱います。 null 許容注釈の効果があるのは、警告によってビルドが止まる場合だけです。無視すると、どの参照が null になり得るかを偽って示す飾りになってしまいます。 -
C# 12 の標準機能を使います。 ファイルスコープの名前空間、プライマリコンストラクター、コレクション式、
requiredメンバーを使います。DTO と値オブジェクトにはレコードを使います。データを比較するときは値の等価性が必要であり、手書きのEquals/GetHashCodeの組み合わせは互いにずれていくためです。 -
型のテストとキャストを連ねる代わりに、パターンマッチングを使います。 網羅性が保証された階層に対する switch 式なら、未処理のケースがあるとコンパイラーが警告します。
if/else ifの連鎖では、新しい型が追加されても何も通知されずに処理が抜け落ちます。 -
想定される失敗には Result パターンを、真に例外的な事態には例外を使います。 「見つからない」や「入力が無効」は通常の結果であり、呼び出し元が対処する必要があるため、戻り値の型に含めます。制御フローに例外を使うと、シグネチャから失敗が見えなくなり、発生のたびにスタックの巻き戻しコストがかかります。閉じたレコード階層を使えば、結果を網羅する
switchを記述できます。public abstract record Result<T> { public sealed record Ok(T Value) : Result<T>; public sealed record Fail(string Error) : Result<T>; } var message = GetUser(id) switch { Result<User>.Ok(var user) => $"Found {user.Name}", Result<User>.Fail(var error) => error, }; -
非同期処理は末端まで一貫して非同期にします。
.Resultと.Wait()は、同期コンテキストがあるとデッドロックを引き起こし、それ以外の環境でもスレッドプールのスレッドを消費します。呼び出しチェーンの末端を非同期にする必要があるなら、先頭も非同期にします。 -
すべての非同期パブリックメソッドで
CancellationTokenを受け取り、引き渡します。 これがないと、キャンセルされたリクエストでもデータベースクエリや HTTP 呼び出しが完了まで実行され続け、すでに離れた呼び出し元に処理コストが発生します。 -
API から EF エンティティを公開しないでください。
Selectで DTO に投影します。そうしないと、次にスキーマへ追加されるものまで公開することになり、遅延読み込みによってシリアル化時に N+1 クエリが大量に発生します。 -
テストの失敗ではなく、本番環境のインシデントにつながる EF 固有の注意点: 読み取り専用クエリにはすべて
AsNoTracking()を指定します。大規模な結果セットで変更追跡を行うのは、純粋なオーバーヘッドだからです。複数のコレクションをIncludeする場合はAsSplitQuery()を使います。単一の結合では、直積のように行数が増えるためです。エンティティを読み込んで変更するのではなく、一括変更には.NET 7+のExecuteUpdateAsync/ExecuteDeleteAsyncを使います。論理削除にはグローバルなHasQueryFilterを使い、どのクエリでもフラグの指定を忘れないようにします。 -
1 つのインターフェイスに複数の実装がある場合は、キー付きサービスを使います(.NET 8)。
AddKeyedScoped<INotifier, EmailNotifier>("email")で登録し、[FromKeyedServices("email")]で解決します。列挙型で切り替えるファクトリーより優れています。コンテナーが引き続きライフタイムを管理できるためです。 -
検証済みの型安全な設定には
IOptions<T>を使います。configuration["Some:Key"]は、キーの入力ミスがあった場合、呼び出し箇所で実行時に失敗します。ValidateOnStartを設定したバインド済みオプションクラスなら、起動時にフィールド名とともに失敗します。 -
呼び出しごとに
new HttpClient()を使うのではなく、IHttpClientFactoryを使います。TIME_WAIT接続が残ることでソケットが枯渇するのは典型的な障害であり、高負荷時にしか発生しません。 -
プロファイラーで割り当てが問題だとわかった箇所では、
Span<T>/Memory<T>とプーリングを使います。 コードが複雑になるため、原則としてではなく、測定結果がある箇所でその複雑さを受け入れてください。