migrate-nullable-references
C#プロジェクトでNullable Reference Types(NRT)を有効化し、CS8602・CS8618・CS86xx系の警告をファイル単位またはプロジェクト全体で体系的に解消します。既存コードベースへのNRT導入、API nullabilityアノテーションの付与、null免除演算子の整理、依存関係の新しいnullableアノテーションへの対応など、移行作業全般に使用してください。なお、NRTが既に完全導入済みのプロジェクトや、C# 7.3以前のプロジェクト、警告を修正せずに抑制するだけの用途には使用しないでください。
description の原文を見る
> Enable nullable reference types in a C# project and systematically resolve all warnings. USE FOR: adopting NRTs in existing codebases, file-by-file or project-wide migration, fixing CS8602/CS8618/CS86xx warnings, annotating APIs for nullability, cleaning up null-forgiving operators, upgrading dependencies with new nullable annotations. DO NOT USE FOR: projects already fully migrated with zero warnings (unless auditing suppressions), fixing a handful of nullable warnings in code that already has NRTs enabled, suppressing warnings without fixing them, C# 7.3 or earlier projects. INVOKES: Get-NullableReadiness.ps1 scanner script.
SKILL.md 本文
Nullable Reference Migration
既存のコードベースでC# nullable reference types(NRT)を有効にし、すべての警告を体系的に解決します。その結果、<Nullable>enable</Nullable>が設定され、nullable警告がゼロであり、公開API表面が正確にアノテーションされたプロジェクト(またはソリューション)が得られます。これにより、コンパイラと利用者に信頼性の高いnullability情報が提供されます。
使用すべき場合
- 既存のC#プロジェクトまたはソリューションでnullable reference typesを有効にする
- 機能を有効にした後、CS86xx nullable警告を体系的に解決する
- ライブラリの公開API表面にアノテーションを付けて、利用者が正確なnullability情報を得られるようにする
- nullableアノテーションが追加された依存関係をアップグレードして、新しい警告が出現した場合
- 既にNRTが有効になっているコードベースの抑制を分析し、削除できるかどうかを判断する
使用すべきでない場合
- プロジェクトに既に
<Nullable>enable</Nullable>が設定されており、警告がゼロである — 移行は完了しているため。ただし、不要な抑制を削除する目的で再検査する場合を除く(Step 6参照) - ユーザーが警告を修正せずに抑制したいだけである — これに対してはお勧めできません
- コードターゲットがC# 7.3以前である(nullable reference typesをサポートしていない)
入力
| 入力 | 必須 | 説明 |
|---|---|---|
| プロジェクトまたはソリューションパス | はい | 移行対象の.csproj、.sln、またはビルドエントリーポイント |
| 移行スコープ | いいえ | project-wide(デフォルト)またはfile-by-file — ロールアウト戦略を制御 |
| ビルドコマンド | いいえ | プロジェクトのビルド方法(例:dotnet build、msbuild、またはリポジトリ固有のビルドスクリプト)。提供されていない場合はリポジトリから自動検出 |
| テストコマンド | いいえ | テストの実行方法(例:dotnet test、またはリポジトリ固有のテストスクリプト)。提供されていない場合はリポジトリから自動検出 |
ワークフロー
🛑 ランタイム動作の変更はありません。 NRT移行は厳密にはメタデータとアノテーションの演習です。生成されるILは変わっていけません — 新しい分岐なし、新しいnullチェックなし、制御フローの変更なし、メソッド呼び出しの追加または削除なし。受け入れ可能な変更は、nullableアノテーション(
?)、nullableアトリビュート([NotNullWhen]など)、!演算子(メタデータのみ)、および#nullableディレクティブのみです。移行中に欠落しているランタイムnullガードまたは潜在的なバグを発見した場合、インラインで修正しないでください。代わりに、// TODO: Consider adding ArgumentNullException.ThrowIfNull(param)コメントを挿入することを提案してください。アノテーション個別の変更として処理できるようにしてください。 behavioral修正をアノテーションコミットに混在させないことは決して行わないでください。
コミット戦略: 各論理的境界でコミットしてください —
<Nullable>を有効にした後(Step 2)、参照解除警告を修正した後(Step 3)、宣言にアノテーションを付けた後(Step 4)、nullableアトリビュートを適用した後(Step 5)、抑制をクリーンアップした後(Step 6)。これにより、各コミットが焦点を当てた状態で確認可能となり、後のステップで設計問題が明らかになった場合にも作業が失われません。ファイル単位の移行では、各ファイルまたは関連ファイルのバッチを個別にコミットしてください。
Step 1: 準備状況の評価
オプション:
scripts/Get-NullableReadiness.ps1 -Path <project-or-solution>を実行して、以下のチェックを自動化します。スクリプトは<Nullable>、<LangVersion>、<TargetFramework>、<WarningsAsErrors>設定とディレクティブ#nullable disable、!演算子、および#pragma warning disable CS86xx抑制の数をレポートします。機械可読出力は-Jsonを使用してください。
- プロジェクトがどのようにビルドおよびテストされるかを確認します。ビルドスクリプト(例:
build.cmd、build.sh、Makefile)、.slnファイル、または個別の.csprojファイルを探します。リポジトリがカスタムビルドスクリプトを使用している場合は、このワークフロー全体でdotnet buildの代わりにそれを使用してください。 dotnet --versionを実行して、SDKがインストールされていることを確認します。Nullable reference types(NRT)にはC# 8.0以上(.NET Core 3.0/.NET Standard 2.1以降)が必要です。.csproj(またはプロパティがリポジトリレベルで設定されている場合はDirectory.Build.props)を開き、<LangVersion>と<TargetFramework>を確認します。プロジェクトがマルチターゲットの場合、すべてのTFMを記録します。
言語バージョンまたはターゲットフレームワークが不十分な場合は停止してください。
<LangVersion>が8.0未満の場合、またはプロジェクトが明示的な<LangVersion>を指定せずにC# 7.xをデフォルトとするフレームワーク(例:.NET Framework 4.x)をターゲットとしている場合、NRTをそのまま有効にすることはできません。変更内容を明示的にユーザーに伝えてください(<LangVersion>8.0</LangVersion>以上に設定するか、.NET Core 3.0+/.NET 5+に再ターゲットしてください)。移行を続行したいのか、中止したいのかをユーザーに確認してください。黙って進行したり、アップデートが受け入れ可能であると仮定しないでください。
<Nullable>が既に設定されているかどうかを確認します。enableに設定されている場合は、Step 5にスキップして残りの警告を監査してください。- プロジェクトタイプを決定します — これは移行全体を通じてアノテーション優先度を形成します:
- ライブラリ:公開API契約をまず優先します。公開パラメータまたは戻り値型の
?は、利用者が依存する契約変更です。正確で保守的になってください。 - アプリケーション(web、console、desktop):境界でのnullセーフティを優先します — 逆シリアル化、データベースクエリ、ユーザー入力、外部APIレスポンス。内部配管は、より自由にアノテーションできます。
- テストプロジェクト:アノテーション精度は低優先度です。テストセットアップとアサーション内で
!をより自由に使用してください(nullは予期されません)。テストコードが清潔にコンパイルされることに焦点を当てます。
- ライブラリ:公開API契約をまず優先します。公開パラメータまたは戻り値型の
Step 2: ロールアウト戦略を選択します
コードベースサイズとアクティビティレベルに基づいて、以下の戦略のいずれかを選択します。戦略をユーザーに推奨し、続行前に確認します。
マルチプロジェクトソリューション: 依存関係の順序で移行します — 共有ライブラリとコアプロジェクトを最初に、その次にそれらを使用するプロジェクトを実施します。依存関係に最初にアノテーションを付けると、コンシューマーの連鎖警告が排除され、作業を二重に行うことを防ぎます。
戦略に関係なく、中心から外側に向かって作業を開始します:コアドメインモデル、DTO、および依存関係が少ないが広く使用される共有ユーティリティタイプから開始します。これらに最初にアノテーションを付けると、コードベース全体の連鎖警告が排除され、最大の努力回報を得られます。次に、コアタイプに依存する高レベルサービス、コントローラ、およびUIコードに移行します。このアプローチは、各ステップでの警告の数を最小限に抑え、大型プロジェクト全体の有効化からの警告の洪水に圧倒されることを防ぎます。プロジェクトごと、またはレイヤーごとに少なくとも1つのPRを作成して、変更セットが確認可能で焦点を当てた状態を保つようにしてください。必要なアノテーションが比較的少ない場合、単一のプロジェクト全体の有効化と単一のPRが適切かもしれません。
戦略A — プロジェクト全体の有効化(小~中規模プロジェクト)
プロジェクトが大体50個未満のソースファイルを有しているか、チームが1パスで完了したい場合に最適です。
.csprojの<PropertyGroup>に<Nullable>enable</Nullable>を追加します。- ビルドし、すべての警告に一度に対応します。
戦略B — 警告優先、次にアノテーション(大規模またはアクティブなプロジェクト)
コードベースが大規模であるか、複数の貢献者による活発な開発中の場合に最適です。
.csprojに<Nullable>warnings</Nullable>を追加します。これは、型セマンティクスを変更せずに警告を有効化します。- ビルドし、Step 3以降のすべての警告を修正します。
<Nullable>enable</Nullable>に変更して、アノテーションを有効にします — これは2番目の警告の波をトリガーします。- Step 4以降のアノテーション段階の警告を解決します。
戦略C — ファイル単位(非常に大規模なプロジェクト)
プロジェクト全体の有効化が管理不可能な数の警告を生成する大規模なレガシーコードベースに最適です。
- プロジェクトレベルで
<Nullable>disable</Nullable>を設定します(またはそのままにします)。 - 移行される各ファイルの最上部に
#nullable enableを追加します。 - ファイルを依存関係の順序で優先順位付けします:共有ユーティリティとモデルを最初に、次に高レベルコンシューマーを優先します。
ビルドチェックポイント:
<Nullable>を有効にした後(または最初のファイルバッチに#nullable enableを追加した後)、クリーンビルドを実行します(例:dotnet build --no-incremental、またはまずbin/objを削除)。インクリメンタルビルドは変更されたファイルのみを再コンパイルし、タッチされていないファイル内の警告を隠します。初期警告カウントを記録してください — これは作業の基準です。警告を修正しない前に、プロジェクトがまだコンパイルされることを確認せずに進めないでください。このワークフローのすべての後続ビルドチェックポイントにはクリーンビルドを使用してください。
Step 3: 参照解除警告を修正します
優先順位付け: 依存関係の順序でファイルを処理します — 他のコードが依存するコアモデルと共有ユーティリティから開始し、次に高レベルコンシューマーに移行します。各ファイル内では、公開および保護メンバーを最初に修正してください(これらが契約を定義します)。次に内部および非公開メンバーを修正します。この順序は連鎖警告を最小限に抑えます:コアタイプのアノテーションを修正すると、コンシューマー内の警告が自動的に解決されることが多いです。
プロジェクトをビルドし、参照解除警告に対応します。これらが最も一般的です:
| 警告 | 意味 | 典型的な修正 |
|---|---|---|
| CS8602 | null である可能性のある参照の参照解除 | アノテーションのみの修正を優先します:nullが有効な場合、アップストリーム型をnullableにする(T?)、またはこの時点で値がnullでないことを確認できる場合は!を使用します。nullチェックまたは?.を追加するとランタイム動作が変わります — 別個のコミット用に予約してください(ゼロ動作変更ルールを参照) |
| CS8600 | 可能性のあるnullを非nullableタイプに変換 | nullが有効な場合はターゲットタイプに?を追加するか、nullでないことを確認できる場合は!を使用します。nullガードを追加するとランタイム動作が変わります |
| CS8603 | 可能性のあるnull参照戻り値 | メソッドが本当にnullを返すことができる場合、戻り値の型をnullableに変更します(T?)。メソッドが本当にnullを返すことができる場合、!で抑制しないでください — 戻り値の型を代わりに修正してください。これはNRT移行における最も重要な単一のルールです:非nullable戻り値の型は、すべての呼び出し元に対してnullが返されることはないという約束です |
| CS8604 | 可能性のあるnull参照引数 | nullが有効な場合はパラメータをnullableにマークするか、引数が確実に非nullである場合は!を使用します。渡す前にnullチェックを追加するとランタイム動作が変わります |
❌ 参照解除警告の素早い修正として
?.を使用しないでください。obj.Method()をobj?.Method()に置き換えると、ランタイム動作が暗黙的に変わります — 呼び出しはスローする代わりにスキップされます。nullを意図的に許容する場合のみ?.を使用してください。
❌ 警告を沈黙させるために
!を撒き散らさないでください。 各!は値がnullでないというクレームです。そのクレームが間違っている場合、NullReferenceExceptionを隠しています。nullチェックを追加するか、代わりに型をnullableにしてください。
❌ 戻り値の型を非nullableに保つために
return null!を使用しないでください。 メソッドがnullを返す場合、戻り値の型はT?である必要があります。return null!を記述することは、null参照解除演算子の背後にnullを隠します — 呼び出し元は署名を信頼し、nullチェックをスキップし、ランタイムでNullReferenceExceptionを取得します。これはnull!、default!、および非nullableポジションでコンパイラにnullを受け入れさせるキャストに適用されます。戻り値で!の唯一の受け入れ可能な使用は、値が証明可能にnullでないがコンパイラが理由を見ることができない場合です。
⚠️ ランタイム型を変更するつもりでない限り、値型に
?を追加しないでください。 参照型の場合、?はメタデータのみです。値型(int、enums、structs)の場合、?は型をNullable<T>に変更し、メソッドシグネチャ、バイナリレイアウト、およびボクシング動作を変更します。
各警告に対する決定フローチャート:
- ここでnullは設計上の有効な値ですか?
- はい → 宣言に
?を追加します(nullableにします)。 - いいえ → ステップ2に進みます。
- 不確定 → 続行する前にユーザーに確認します。
- はい → 宣言に
- この時点で値がnullでないことを証明できますか?
- はい、コンパイラが見ることができないコードパスで →
!を追加し、理由を説明するコメントを付けます。 - はい、ガードを追加することで → nullチェック(
if、??、is not null)を追加します。 - いいえ → 型はnullableであるべきです(ステップ1に戻ります — 答えは「はい」です)。
- はい、コンパイラが見ることができないコードパスで →
ガイダンス:
- null-許容演算子(
!)よりも明示的なnullチェック(if、is not null、??)を優先します。 - null-許容演算子は、値がnullでないことを証明できるがコンパイラが見ることができない場合のみ使用し、なぜそうなのかを説明するコメントを追加します。
- Guard句ライブラリ(例:Ardalis.GuardClauses、Dawn.Guard)は、パラメータを
[NotNull]で装飾することが多く、ガード呼び出し後のnull状態を縮小します。Guard.Against.NullOrEmpty(value, nameof(value))の後、コンパイラは既にstring?をstringに縮小します — 後続の割り当てに冗長な!を追加しないでください。ガードメソッドが[NotNull]を使用しているかどうかを確認してから、コンパイラが助けが必要だと仮定します。 - メソッドが本当にnullを返す場合、戻り値の型を
T?に変更します — 非nullableシグネチャの背後にnullを隠さないでください。 Debug.Assert(x != null)は、ifチェックと同じようにnull状態ヒントとしてコンパイラに機能します。メソッドまたはブロックの最上部で使用して、フロー分析器にインバリアントについて知らせ、そのスコープ内の後続の!演算子を排除します。注:Debug.Assertはコンパイラに通知しますが、リリースビルドから削除されます — ランタイムでnullから保護しません。公開API境界の場合、明示的なnullチェックまたはArgumentNullExceptionを優先します。- 内部メソッドのすべての呼び出しサイトで
!を追加している場合、代わりにそのパラメータをnullableにすることを検討します。!を予約してください。コンパイラが非nullnessを本当に証明できないケースのために。 - ブール戻り値ヘルパーメソッドの結果がnullableパラメータが非nullであることを保証する場合(例:
if (IsValid(x))はx != nullを意味します)、すべての呼び出しサイトで!を使用するよりも、ヘルパーのパラメータに[NotNullWhen(true)]を追加することを優先します。これはメタデータのみの変更です(動作変更なし)。コンパイラに実際のフロー情報を与えながら、ダウンストリームの!演算子を排除します。 - コンストラクション後に常に設定されるフィールド(例:フレームワーク、
Init()メソッド、またはビルダーパターン)については、すべての使用サイトで!を追加するよりも、フィールド宣言の= null!を優先します。50回アクセスされるフィールドは、50個のfield!アサーションではなく1つの= null!を持つべきです。これはフィールドを型システムで非nullableに保ちながら、「アクセス前に設定されることを保証します」ということをコンパイラに伝えます。可能な場合は初期化メソッド上の[MemberNotNull]とペアにしてください。 - ジェネリックメソッドが制約なしの型パラメータで
defaultを返す場合(例:FirstOrDefault<T>)、T?ではなく[return: MaybeNull] Tを使用します。T?を制約なしのジェネリックに記述することは、値型シグネチャをNullable<T>に変更し、メソッドシグネチャとバイナリレイアウトを変更します。[return: MaybeNull]は元のシグネチャを保持しながら、戻り値が参照型に対してnullである可能性があることを通信します。 - LINQの
Where(x => x != null)はT?をTに縮小しません — コンパイラはジェネリックメソッドに渡されたラムダを通じてnullability を追跡できません。source.OfType<T>()を使用してnullを正しい型縮小でフィルタリングします。
ビルドチェックポイント: 参照解除警告を修正した後、ビルドして、CS8602/CS8600/CS8603/CS8604警告が残らないことを確認してからアノテーション警告に進みます。
Step 4: 宣言にアノテーションを付けます
各メンバーの意図されたnullabilityを設計目的に基づいて決定することから始めてください — このパラメータはnullを受け入れるべきですか?この戻り値はnullになることがありますか?それに応じてアノテーションを付けてから、結果の警告に対処します。警告があなたのアノテーションを駆動させないでください;これは過度な?アノテーションまたはコンパイラを沈黙させるための!の散在につながります。
ユーザーに確認する場合: API契約を推測しないでください。命名規則だけからnullability意図を推測することは決してしないでください — 意図が明示的なコードまたはドキュメントにない場合、ユーザーに確認します。具体的には、次の前に確認してください:(1)公開メソッドの戻り値の型をnullableに変更するか、公開パラメータに
?を追加する — これは利用者が依存するAPI契約を変更します;(2)設計意図が不明な場合、プロパティがnullableと必須のどちらであるべきかを決定する;(3)nullが有効な状態であるかどうかをコンテキストから判断できない場合、nullチェックと!のどちらを選ぶか。内部/非公開メンバーで答えが使用から明らかな場合は、確認なしで進めます。
❌ 警告があなたのアノテーションを駆動させないでください。 まず各メンバーの意図されたnullability を決定してから、アノテーションを付けてください。警告を消すためにどこでも
?を追加すると、目的に反します — 呼び出し元は不要なnullチェックを追加する必要があります。どこでも!を追加するとバグが隠されます。
⚠️ 戻り値型は意味的なnullability を反映する必要があり、コンパイラ満足だけではありません。 一般的な間違いは、実装が
default!またはコンパイラを満たすキャストを使用しているため、戻り値型から?を削除することです。メソッドが設計上nullを返すことができる場合、戻り値型はnullableである必要があります — コンパイラが警告するかどうかに関わらず。重要なパターン:
- メソッド名が
*OrDefault(FirstOrDefault、SingleOrDefault、FindOrDefault) → 戻り値型はnullableである必要があります(T?、object?、dynamic?)。「or default」は参照型に対して「or null」を意味するため。ExecuteScalarおよび同様のデータベースメソッド →DBNull.Valueまたはnullの行がマッチしない場合の結果がobject?であるため、戻り値型はobject?である必要があります。Find、TryGet*(outパラメータ)、およびルックアップメソッド → アイテムが存在しない可能性があるとき、戻り値型はnullableであるべきです。- 設計されたまたはドキュメント化されたnullを失敗、未発見、または空入力で返すメソッド → nullableな戻り値型。
コンパイラは、実装が
!またはdefault!の背後にnullを隠すとき、戻り値型の欠落した?をキャッチできません。これにより、利用者にとってアノテーションが間違っています — 彼らはnon-nullableシグネチャを信頼し、nullチェックをスキップし、ランタイムでNullReferenceExceptionを取得します。
⚠️ 既存の
ArgumentNullExceptionチェックを削除しないでください。 非nullableパラメータアノテーションはコンパイル時のヒントのみです — ランタイムでnullを防ぎません。古いC#バージョン、他の.NET言語、リフレクション、または!を使用する呼び出し元はまだnullを渡すことができます。
⚠️ 公開API方法をフラグしてruntime nullvalidationを見落とします — しかし、チェックを追加しないでください。 アノテーション中に、各
publicおよびprotectedメソッドを確認します:パラメータが非nullableである場合(T、T?ではない)、ランタイムnullチェック(例:ArgumentNullException.ThrowIfNull(param)またはif (param is null) throw new ArgumentNullException(...))があるはずです。そがない場合、ランタイムでnullが渡された場合、メソッドの深い中でNullReferenceExceptionが発生します。明確なArgumentNullExceptionを入り口で発生させます。nullガードを追加することはランタイム動作の変更であり、NRT移行の一部であってはなりません。代わりに、ユーザーにサイトに// TODO: Consider adding ArgumentNullException.ThrowIfNull(param)コメントを挿入することを提案します。これは特にnullが古いC#バージョン、他の.NET言語、リフレクションを使用する呼び出し元によって渡される可能性があるライブラリにとって重要です。
定義された動作でnullを処理するメソッドはnullableパラメータを受け入れるべきです。 メソッドがnull入力を適切に処理する場合 — nullを返す、デフォルトを返す、失敗結果を返す、またはスローする代わりに — パラメータは
T?であるべき、Tではなく。BCLはこの規則に従います:Path.GetPathRoot(string?)はnull入力に対してnullを返しますが、Path.GetFullPath(string)はスローします。nullが例外の原因になる場合のみ、非nullableパラメータを使用してください。パラメータを非nullableでマークするとき、メソッドが実際にnullを許容していると、呼び出し元は呼び出しの前にnullチェックを追加するよう強制されます。グレーゾーン: パラメータが検証、サニタイズ、またはnull用にドキュメント化されていない場合、考えてください:(1)独自のコードベースでnullが渡されていますか?はい → nullable。(2)nullが呼び出し元による「デフォルト」またはノープレースホルダーとして使用される可能性がありますか?はい → nullable。(3)同じ領域の同様のメソッドはnullを受け入れていますか?はい → 一貫性のためnullable。(4)メソッドはnullに大きく無視しており、単に動作する傾向があり、nullはAPIの目的に対して意味をなさない場合 → non-nullable。nullableとnon-nullableの間に疑問がある場合、nullableを優先します — これはより安全であり、後で厳密にすることができます。
参照解除警告が解決された後、アノテーション警告に対応します:
| 警告 | 意味 | 典型的な修正 |
|---|---|---|
| CS8618 | 非nullableフィールド/プロパティはコンストラクタで初期化されていません | メンバーを初期化するか、nullableにする(?)、またはrequiredを使用します(C# 11+)。コンストラクタの外で常に設定されるフィールド(例:フレームワークライフサイクルメソッド、Init()呼び出し、またはビルダーパターン)については、= null!を使用してインテントを宣言しながら、フィールドをすべての使用サイトで非nullableに保ちます。ヘルパーメソッドがフィールドを初期化する場合、[MemberNotNull(nameof(field))]でデコレートして、呼び出し後にフィールドが非nullであることをコンパイラに知らせます |
| CS8625 | null リテラルを非nullableタイプに変換できません | ターゲットをnullableにするか、非null値を指定します |
| CS8601 | 可能性のあるnull参照の割り当て | CS8600と同じテクニック |
各タイプに対して、このメンバーはnullになるべきですか?
- はい → 宣言に
?を追加します。 - いいえ → すべてのコンストラクタパスで初期化されていることを確認するか、
requiredとマークします(C# 11+)。 - いいえ、しかしコンストラクタ後に設定されます(例:フレームワークメソッド、ビルダー、2段階初期化パターン) → フィールド宣言で
= null!を使用します。これはフィールドの型を使用されるすべての場所で非nullableに保ちながら、「これはアクセス前に設定されることを保証します」とコンパイラに伝えます。これははるかに、すべての使用サイトで!を追加するよりも優れています — 50回アクセスされるフィールドは50個の!演算子ではなく1個の= null!が必要です。初期化が特定のメソッドで行われる場合、そのメソッドで[MemberNotNull(nameof(field))]も検討してください。
アノテーション努力を公開および保護API上で最初に焦点を当てます — これらが利用者が依存する契約を定義します。内部および非公開コードはより自由に!を許容できます。外部の呼び出し元に影響しないため。
公開ライブラリ:破壊的な変更を追跡します。 プロジェクトが他者に使用されるライブラリである場合、
nullable-breaking-changes.mdファイル(またはそれと同等)を作成し、利用者に影響を与える可能性のあるすべての公開API変更を記録します。参照型への?の追加はメタデータのみであり、バイナリ破壊的ではありませんが、NRTが有効な利用者にとってはソース破壊的です — 彼らは新しい警告またはエラーを取得します。ドキュメント化する重要な変更:
- 戻り値型が
TからT?に変更されました(利用者はnullを処理する必要があります)- パラメータが
T?からTに変更されました(利用者はnullを渡すことができなくなります)- パラメータが
TからT?に変更されました(利用者の既存のnullチェックは不要になります — 低影響ですが注目に値します)- 値型パラメータまたは戻り値型に
?が追加されました(TをNullable<T>に変更します — バイナリ破壊的)- 新しい
ArgumentNullExceptionガードが以前になかった場所に追加されました- アノテーション中に発見および修正された動作変更(例:silentlyがnullを受け入れていたメソッドはnullをスローします)
このファイルを利用者に確認するために提示します。これはリリースノートの基礎としても機能する場合があります。
以下に特別な注意を払ってください:
- DTO対ドメインモデル:クラスのロールに応じて異なるnullability戦略を適用します。DTOおよびシリアル化モデルは信頼境界を越えます(JSON、フォーム、外部API) — 宣言型に関わらず、逆シリアル化データは常にnullになる可能性があるため、プロパティはデフォルトでnullableです。
required(C# 11+)、[JsonRequired](.NET 7+)、またはランタイム検証を使用して、非nullconstraintを強制します。ドメインモデルは内部インバリアントを表します — 非nullableプロパティをコンストラクタ強制で優先し、無効な状態を表現不可能にします。この区別は移行がほとんど間違っている場所です:DTOをドメインモデルとして扱うとランタイムNullReferenceExceptionが発生します;ドメインモデルをDTOとして扱うと不要なnullチェックがあらゆる場所に発生します。 - イベントハンドラーおよびデリゲート:パターン
EventHandler? handler = SomeEvent; handler?.Invoke(...)は慣例的です。 - 構造体参照型フィールド:構造体の参照型フィールドは
default(T)を使用するとnullです。構造体に対してdefaultが有効な用途の場合、これらのフィールドはnullableである必要があります。defaultが決して期待されない場合(構造体は特定のAPIのみによって作成されます)、不要なnullチェックですべてのコンシューマーを負担させないために、非nullableに保ちます。 - Dispose後の状態:フィールドまたはプロパティがオブジェクトの全有効時間に対して非nullであるが、
Dispose後にnullになる可能性があります。非nullableに保ちます。Dispose後にオブジェクトを使用することは契約違反です — その場合に注釈を弱めないでください。 - オーバーライドおよびインターフェース実装:オーバーライドは、ベースメソッドが宣言するより厳密な(非nullable)タイプを返すことができます。実装がnullを返さないが、ベース/インターフェースが
T?を返す場合、オーバーライドをTを返すものとして宣言できます。パラメータ型はベースと正確に一致する必要があります。 - 広くオーバーライドされた仮想戻り値型:多くのクラスがオーバーライドする仮想/抽象メソッドの場合、既存のオーバーライドが実際にnullを返すかどうかを検討します。一般的にそうする場合(
Object.ToString()のように)、戻り値をT?と注釈してください — 呼び出し元は知る必要があります。nullオーバーライドが非常にまれな場合(Exception.Messageのように)、Tとして注釈してください。広くオーバーライドされる仮想については不確定なとき、T?を優先します。 IEquatable<T>およびIComparable<T>:参照型はIEquatable<T?>およびIComparable<T?>(nullableでT)を実装するべきです。呼び出し元は一般的にEqualsとCompareToにnullを渡すため。Equals(object?)オーバーライド:Equals(object? obj)オーバーライドのパラメータに[NotNullWhen(true)]を追加します —Equalsがtrueを返す場合、引数は確実に非nullです。これにより、呼び出し元は等性テスト後の冗長なnullチェックをスキップできます。
ビルドチェックポイント: 宣言にアノテーションを付けた後、ビルドして、CS8618/CS8625/CS8601警告が残らないことを確認してからnullableアトリビュートに進みます。
Step 5: 高度なシナリオのためにnullableアトリビュートを適用します
単純な?アノテーションがnull契約を表現できない場合、System.Diagnostics.CodeAnalysisからアトリビュートを適用します — 完全なアトリビュートテーブル([NotNullWhen]、[MaybeNullWhen]、[MemberNotNull]、[AllowNull]、[DisallowNull]、[DoesNotReturn]など)ガイダンス付きについてはreferences/nullable-attributes.mdを参照してください。
ビルドチェックポイント: nullableアトリビュートを適用した後、アトリビュートが対象の警告を解決し、新しい警告を導入しなかったかどうかを確認するためにビルドします。
Step 6: 抑制をクリーンアップします
オプション:
scripts/Get-NullableReadiness.ps1を再実行して、プロジェクト全体の#nullable disableディレクティブ、!演算子、および#pragma warning disable CS86xx抑制の現在のカウントを取得します。
- 一時的な回避策として追加された
#nullable disableディレクティブまたは!演算子を検索します。 - 各々について、抑制がまだ必要かどうかを決定します。
- 不要な抑制を削除してください。残すものについては、なぜ必要かを説明するコメントを追加してください。
#pragma warning disable CS86を検索して、抑制されたnullable警告を見つけ、根底にある問題を代わりに修正できるかどうかを評価します。
ビルドチェックポイント: 抑制を削除した後、再度ビルドします —
#nullable disableまたは!を削除すると、修正する必要がある新しい警告が表示される場合があります。
Step 7: 検証します
- プロジェクトをビルドして、nullableな警告がゼロであることを確認します。
- プロジェクトファイルに
<WarningsAsErrors>nullable</WarningsAsErrors>を追加します(またはリポジトリ全体についてはDirectory.Build.props)。これはnullableな回帰を永続的に防ぎます。これはdotnet build /warnaserror:nullableと同等です。 - 既存のテストを実行して、回帰がないことを確認します。
- プロジェクトがライブラリの場合、公開API表面を検査して、nullableアノテーションが意図した契約と一致することを確認します(nullを受け入れるパラメータは
T?、nullを拒否するパラメータはT)。
報告成功の前に検証してください。 警告がゼロであるだけは、移行が正しいことを意味しません。成功を報告する前に:(1)公開APIシグネチャをスポットチェックしてください —
?アノテーションが実際の設計意図と一致することを確認し、コンパイラの沈黙だけではなく;(2)動作変更を加える?.演算子が追加されていないことを確認します(差分内で?.を検索);(3)ArgumentNullExceptionチェックが削除されていないことを確認します;(4)!演算子が希少であり、各々に正当化コメントがあることを確認します。
検証
- プロジェクトファイルに
<Nullable>enable</Nullable>が含まれます(またはファイル単位の戦略の場合は#nullable enable) - ビルドはCS86xx警告がゼロを出力します
- プロジェクトファイルに
<WarningsAsErrors>nullable</WarningsAsErrors>が追加されます(回帰を防ぎます) - テストは回帰なしで合格します
-
#nullable disableディレクティブは、正当化コメントで残されているもの以外は残りません - Null許容演算子(
!)は希少であり、各々に正当化コメントがあります - 公開APIシグネチャはnull契約を正確に反映します
- 公開ライブラリの場合:破壊的な変更は
nullable-breaking-changes.mdでドキュメント化され、ユーザーに確認されます
コードレビューチェックリスト
Nullableな移行変更は、典型的な差分よりも広いレビューが必要です:
- 動作変更がないことを確認します:
?と!のみが追加され、偶発的な?.なし、削除されたnullチェックなし、新しい分岐なしであることを確認します。生成されたILは、nullableメタデータを除き、変わりません。 - 明示的なアノテーション変更をレビューします:パラメータまたは戻り値型に追加されたすべての
?について、意図した設計と一致することを確認します。メソッドは本当にnullを受け入れていますか?本当にnullを返すことができますか? - スコープ内の変わらないAPIをレビューします:
<Nullable>enable</Nullable>を有効化すると、そのスコープ内の注釈のない参照型がすべて暗黙的に非nullableになります。変わらない公開メンバーを実際にnullを受け入れるパラメータのためスキャンしますが、アノテーションされていませんでした。
ライブラリの破壊的な変更
ライブラリについては、references/breaking-changes.mdを参照してください — NRTアノテーションは公開API契約の一部であり、不正なアノテーションは利用者にとってソース破壊的な変更です。
一般的な落とし穴
| 落とし穴 | ソリューション |
|---|---|
警告を沈黙させるためにどこでも!を撒き散らす | null許容演算子はバグを隠します。nullチェックを追加するか、代わりに型をnullableにしてください |
警告をすばやく排除するためにすべてをT?とマークする | ?で過度にアノテーションすると目的に反します — 呼び出し元は不要なnullチェックを追加する必要があります。nullが有効な値の場合のみ?を使用します |
| コンストラクタがすべての非nullableメンバーを初期化しない | すべてのコンストラクターでフィールドとプロパティを初期化するか、requiredを使用します(C# 11+)。またはメンバーをnullableにします |
| シリアル化はコンストラクタをバイパスします — 非nullableはランタイムセーフティではない | シリアライザーはコンストラクタを呼び出さずにオブジェクトを作成するため、非nullableなDTOプロパティはランタイムでもnullになる可能性があります。Step 4の「DTOとドメインモデル」詳細なガイダンスを参照してください |
| 生成されたコードは警告を出力します | 生成されたファイルは、<auto-generated>コメントを含む場合、nullableな分析から自動的に除外されます。警告が続く場合、生成されたファイルの上部に#nullable disableを追加するか、.editorconfigをgenerated_code = trueで構成します |
| マルチターゲットプロジェクトおよび古いTFM | NRTアノテーションはC# 8.0+で古いTFM(例:.NET Standard 2.0)でコンパイルされますが、[NotNullWhen]などのnullableアトリビュートは存在しない場合があります。NuGetからNullableなどのポリフィルパッケージを使用するか、属性を内部的に定義します |
| 依存関係のアップグレード後に警告が再び表示されます | 依存関係はnullableアノテーションを追加しました。これは期待された有益な結果です — Step 3–5に従って新しい警告を修正します |
| アノテーション中に偶発的に動作を変更する | 型に?を追加または式に!を追加することはメタデータのみであり、生成されたILを変更しません。ただし、obj.Method()をobj?.Method()(null条件)に置き換えるとランタイム動作が変わります — 呼び出しはスローする代わりに沈黙裏にスキップされます。nullを意図的に許容する場合のみ?.を使用します。警告の素早い修正としてではなく。 |
値型に?を追加する(enum、struct) | 参照型の場合、?はランタイム効果なしのメタデータアノテーション。intまたはenumなどの値型の場合、?は型をNullable<T>に変更し、メソッドシグネチャ、バイナリレイアウト、およびボクシング動作を変更します。参照型にのみ?を追加していることを二重チェックしてください(値型をnullableにするつもりでない限り)。 |
| 既存のnull引数検証を削除する | 非nullableアノテーションはコンパイル時のみです — 呼び出し元はランタイムでもnullを渡すことができます。既存のArgumentNullExceptionチェックを保ちます。Step 4の詳細を参照してください |
varは割り当てられた式からnullabilityを推測します | varを使用する場合、推測された型は割り当てられた式のnullabilityを含み、明示的にT対T?を宣言するのと比較して驚くかもしれません。宣言でのprecise nullabilityが重要である場合は、varの代わりに明示的な型を使用します |
| nullableではない(nullable無視)ライブラリを使用します | 依存関係がnullableアノテーションにオプトインしていない場合、コンパイラはそのすべての型を「無視」と扱い — 参照解除またはnullの割り当てに対して警告は取得しません。これは虚偽のセーフティ感を与えます。nullを返す概念的にできるメソッド(辞書ルックアップ、FirstOrDefaultスタイルの呼び出し)に対しては、nullableライブラリから戻り値をnullとして扱ってください。依存関係をアップグレードするか、可能な場合は呼び出しをラップします |
Entity Framework Core に関する考慮事項
プロジェクトがEF Coreを使用する場合、references/ef-core.mdを参照してください — NRTを有効にするとデータベーススキーマの推測およびマイグレーション出力を変更できます。
ASP.NET Core に関する考慮事項
プロジェクトがASP.NET Coreを使用する場合、references/aspnet-core.mdを参照してください — NRTを有効にするとMVCモデル検証およびJSONシリアル化動作を変更できます。
その他の情報
- Nullable reference types — 機能の概要、nullableコンテキスト、およびコンパイラ分析
- Nullable reference types (C# reference) — nullableアノテーションおよび警告コンテキストの言語リファレンス
- Nullable migration strategies
- Embracing Nullable Reference Types — Mads Torgersen による採用タイミングおよびエコシステムに関する考慮事項のガイダンス
- Resolve nullable warnings
- Attributes for nullable static analysis
- ! (null-forgiving) operator — 演算子の言語リファレンスとその使用時機
- EF Core and nullable reference types
- .NET Runtime nullable annotation guidelines — .NET ライブラリ自体にアノテーションを付ける際に使用されるアノテーション原則
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- dotnet
- リポジトリ
- dotnet/skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/dotnet/skills / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。