mcpField
C
csharp-nullable-reference-types

csharp-nullable-reference-types

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

csharp-nullable-reference-types について

C# の nullable reference types を導入し、null 許容契約を設計するためのスキルガイドです。段階的な移行、API 属性、レガシー連携、互換性の確認を扱います。

主な機能

  1. 01nullable 注釈とフロー解析
  2. 02段階的な NRT 移行
  3. 03API の null 契約設計
  4. 04コード分析属性の指針
  5. 05公開 API の互換性確認

活用シーン

  1. 01レガシーコードへの NRT 導入
  2. 02nullable 警告とガードの確認
  3. 03ライブラリ API の null 動作の注釈

インストール

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

$npx skills add https://github.com/aaronontheweb/dotnet-skills --skill csharp-nullable-reference-types

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

同梱ファイルnrt-migration-playbook-reference.mdnullable-attributes-reference.md

詳細

C# Nullable Reference Types

使用する場面

  • まだ導入されていないコードベースに nullable reference types(NRT)を導入するとき
  • T?/nullable 注釈を使用する C# コードを記述またはリファクタリングするとき
  • System.Diagnostics.CodeAnalysis の nullable 属性で API に注釈を付けるとき
  • null 許容性のコントラクトが重要な public/internal API を設計するとき
  • 注釈のない API やレガシー API をラップし、下流の呼び出し側でも NRT の恩恵を受けられるようにするとき
  • null 状態分析、ガードヘルパー、field キーワードが適切に使用されているかコードをレビューするとき

主な目標

  • シグネチャで null の意図を明示し、実行時の NullReferenceException を防ぎます。
  • 公式の nullable 属性を使用して、型システムで直接表現できないコントラクトを明示します。
  • 大規模な一括書き換えを行わずに、レガシーコードベースへ NRT を段階的に導入します。

null 許容性の基本モデル

null 非許容と null 許容
  • string — null 非許容の参照型です。コンパイラーはインスタンスが null にならないと想定し、null または null の可能性がある値を代入すると警告を出します。
  • string? — null 許容の参照型です。変数が null になる可能性があるため、コンパイラーは逆参照の前に null チェックを要求します。
string name = "Alice";
name = null;          // Warning: assigning null to non-nullable.

string? nickname = null;
Console.WriteLine(nickname.Length); // Warning: possible null dereference.
null 状態分析(フロー)

コンパイラーは、参照が確実に null ではないか、null の可能性があるかを追跡します。null チェックと代入によって、この状態が更新されます。

string? message = GetMessageOrNull();

if (message != null)
{
    // message is definitely non-null in this block.
    Console.WriteLine(message.Length);
}

// Outside the if, message is maybe null again.

null 許容値を逆参照する前に、明示的な null チェック(if (x != null)、is not null、パターンマッチング)を行います。早い段階で null 許容性を絞り込み、null ではない状態を維持します。**null 条件付き代入(C# 14)**を使うと、customer?.Order = CreateOrder(); と記述できます。右辺は、レシーバーが null でない場合にのみ評価されます。

null 抑制演算子(!)

x! は「ここでは x を null ではないものとして扱う」ようコンパイラーに指示します。影響するのは分析のみで、実行時の動作は変わりません。

  • 実際の不変条件によって null でないことが保証され、コンパイラーがそれを認識できない場合にのみ ! を使用します。
  • 警告への一般的な対処として ! を使用しないでください。制御フローのリファクタリング、属性の追加、適切なメンバー初期化を優先します。
_customer = LoadCustomerFromOrm()!; // ORM guarantees this is not null in valid state.
警告を抑制する前にコードを整理する

ガード節やパターンマッチングが成功すれば、現在のスコープ内に null 安全な領域がすでに作られています。! を追加する前に、null 許容値をチェック済みの境界で一度だけ受け渡し、その後のコードでは null 非許容の状態を保ちます。

  • ガード節またはパターンマッチングで早めに絞り込みます。
  • null 許容のフィールドやプロパティは、チェックする前にローカル変数へコピーします。これにより、繰り返し読み取る値が分析中に変わるのを防げます。
  • メソッドの制御フローが複雑な場合は、null ではない経路を、null 非許容のパラメーターを持つローカル関数または private メソッドに分けることを検討します。
  • T?、繰り返しのチェック、! を実装全体に広げず、境界で null を処理します。
public void Process(Order? order)
{
    if (order?.Customer is not { } customer)
    {
        return;
    }

    // The pattern match already proved that customer is non-null here.
    Console.WriteLine(customer.Name);
}

nullable 分析を満たすことだけを目的に関数を切り出さないでください。大規模な実装や分岐の多い実装も簡潔になる場合には、明示的に null 非許容の関数境界を設けます。制御フロー、シグネチャ、または nullable 分析属性では表現できない実際の外部不変条件がある場合にのみ、! を使用します。

プロジェクトの構成

新しいコードでは NRT を有効にします。

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

レガシーコードベースでは、ファイル単位のディレクティブ(#nullable enable、#nullable disable、#nullable enable warnings、#nullable enable annotations)を使って段階的に有効にします。CS86xx の nullable 警告を重視し、新しいプロジェクトでは TreatWarningsAsErrors の設定、または nullable 警告をエラーとして扱うことを検討します。

段階的な導入戦略、#nullable ディレクティブのリファレンス、レガシーとの相互運用、既知の静的分析の制限事項(配列、default(struct))については、nrt-migration-playbook-reference.md を参照してください。

API 設計ルール(シグネチャ)

これらのルールは、パブリック API と内部 API、およびモデルに適用されます。

パラメーター — null を許可しない場合は、null 非許容型を使用し、パブリック API には実行時ガードを追加します。

public void SendEmail(string recipient)
{
    ArgumentNullException.ThrowIfNull(recipient);
    // Implementation
}

null を許可し、意味がある場合は、T? を使用し、null の解釈方法をドキュメントに記載して、適切な null の動作を実装します。

戻り値の型 — メソッドが null を返さない場合は Customer、正当に null を返す可能性がある場合は Customer? を使用します(呼び出し側はチェックする必要があり、コンパイラーによってそのチェックが強制されます)。

public Customer GetRequiredCustomer(Guid id);  // Throws on failure.
public Customer? TryGetCustomer(Guid id);       // Returns null on failure.

プロパティとフィールド — パラメーターと戻り値の型と同じルールに従います。null 非許容メンバーは、コンストラクター、オブジェクト初期化子で使用する required プロパティ、field を使用した遅延プロパティ、または [MemberNotNull] で注釈されたヘルパーを通じて初期化する必要があります。

public class Order
{
    public required string Id { get; init; }
    public required Customer Customer { get; init; }
    public string? Comment { get; init; } // Optional.
}

コントラクトが入力/出力の動作、条件付きの動作、またはメンバーの初期化に依存する場合は、nullable-attributes-reference.md で説明されている null 許容属性を適用します。

ライブラリのパブリック API 互換性

null 許容注釈と null 許容解析属性は、リリース済み API コントラクトの一部として扱ってください。T と T? は同じ CLR 型であるため、注釈のみの変更は一般にバイナリ互換ですが、null 許容が有効なコンシューマーに警告を発生させることで、ソース互換性を損なう可能性があります。コンシューマーが警告をエラーとして扱っている場合、これらの警告がビルド失敗につながることもよくあります。

リリース前に、パブリック API の null 許容性の変更を確認してください。特に、以下の変更には注意が必要です。

  • 出力を T から T? に弱める、または [MaybeNull] を追加する。
  • 入力を T? から T に厳しくする、または [DisallowNull] を追加する。
  • class?、class、notnull などのジェネリック制約を変更する。
  • 仮想メンバー、インターフェイス、デリゲート、および実装の注釈や属性を変更する(不一致がコンパイラー警告を発生させるため)。

以前は null 許容性が注釈されていなかった API に注釈を追加する場合も、同様のソース互換性の問題が生じる可能性があります。注釈付きの API の公開範囲を最後にリリースしたバージョンと比較し、null 許容が有効なコンシューマーでテストしてください。また、意図的にソース互換性を損なう変更については、ライブラリの互換性ポリシーに従ってドキュメントに記載するか、バージョンを変更してください。

field キーワード(C# 14 / .NET 10)

field コンテキストキーワードを使うと、明示的なバッキングフィールドを宣言せずにプロパティアクセサーの本体を記述できます。これは NRT の主要な用途(遅延初期化プロパティ)であり、コンパイラーは特別な null 耐性 解析を実行するため、コンストラクターで不要な CS8618 が発生しません。

public class C
{
    public C() { } // No warning: the getter is null-resilient.
    string Prop => field ??= GetPropValue();
}

field の null 許容性ルールの全容(null 耐性あり/なしのゲッター、[field: AllowNull, MaybeNull] による回避策、セッター/コンストラクターの解析)については、nullable-attributes-reference.md を参照してください。

参照ファイル

  • nullable-attributes-reference.md:System.Diagnostics.CodeAnalysis 属性の完全なカタログです。事前条件(AllowNull、DisallowNull)、事後条件(MaybeNull、NotNull)、条件付き事後条件(NotNullWhen、MaybeNullWhen、NotNullIfNotNull)、ヘルパーメソッド(MemberNotNull、MemberNotNullWhen)、到達不能コード用ヘルパー(DoesNotReturn、DoesNotReturnIf)、および field キーワードの null 許容性ルールを網羅しています。それぞれに意図、パターン、エージェント向けルールが記載されています。
  • nrt-migration-playbook-reference.md:段階的な導入戦略、#nullable ディレクティブのリファレンス、レガシー/注釈なし API との相互運用、古いターゲットフレームワーク向けの null 許容属性のポリフィル(トレードオフと追加前に確認する意思決定プロセスを含む)、既知の静的解析の制限(null 非許容参照の配列、参照フィールドを含む default(struct))、警告への対処、および生成チェックリストの全項目を記載しています。

生成チェックリスト(概要)

  1. プロジェクト — <Nullable>enable</Nullable> が設定されていること。避けられないレガシーコードの周辺でのみ無効にします。
  2. 型 — 必須のパラメーター/戻り値/プロパティには非 null 許容型を使用します。null が有効であり、想定される場合にのみ T? を使用します。
  3. 初期化 — コンストラクター、required とオブジェクト初期化子、field による遅延ゲッター、または [MemberNotNull] ヘルパーを使用します。ドキュメント化された回避策としての場合を除き、null! は避けます。
  4. null チェック — パブリックな境界で明示的なガードを設けます。制御フローで対象を絞り、複雑なコードを読みやすくできる場合にのみ非 null 許容のヘルパーを抽出します。! は明確な不変条件がある場合にのみ使用します。
  5. 属性 — 型システムでは表現できない契約を示すために適用します(リファレンスファイルを参照)。
  6. 相互運用 — BCL/注釈付きライブラリを信頼します。注釈のない API をラップする場合は、独自のガードと属性を追加します。
  7. 警告 — 決して無視しないでください。! や #pragma で抑制するのではなく、設計を修正するか属性を追加します。
  8. 互換性 — リリース済みのライブラリでは、パブリックな null 許容性の変更をソース互換性の破壊につながる可能性がある変更として確認し、null 許容が有効な利用側でテストします。

参考資料

コメント