null 許容の移行戦略

Tip

新しいプロジェクトを開始しますか? .NET 6 以降のテンプレートから作成された新しいプロジェクトには、既に <Nullable>enable</Nullable> が設定されています。 移行戦略は必要ありません。 「null 許容警告を解決する」に進みます。

既存のコードベースを維持していますか? 最初に Null 許容参照型 を読み取り、コンテキスト、注釈、および null 状態を理解します。 この記事では、これらの概念を理解しており、ロールアウトを計画する準備ができていることを前提としています。

null 許容参照型が導入される前に開始された大規模なプロジェクトで null 許容参照型を有効にすると、コンパイラは一度に多くの警告を生成します。 移行とは、作業の順序を決めることです。つまり、既定のコンテキストを選び、警告をファイル単位またはセクション単位で表示し、最終的にプロジェクト全体で<Nullable>enable</Nullable>に統一していくことです。 適切なシーケンスは、コードベースのアクティブ度と、1 回のパスで実行できるリスクによって異なります。

終了状態はすべてのケースで同じです。プロジェクトは <Nullable>enable</Nullable> を設定し、プリプロセッサ ディレクティブ #nullable 含んでいません。

既定のコンテキストを選択する

null 許容コンテキストには、 注釈 ( ? が null 許容参照型を宣言するかどうか) と 警告 (コンパイラが診断を出力するかどうか) という 2 つの独立したフラグがあります。 それらを 1 つの <Nullable> 値として設定します。

既定値 Annotations 警告 最適な用途
disable (暗黙的) オフ オフ このパスで新機能を使用しない安定したライブラリ。
enable on on 頻繁に新しいファイルを含むアクティブなコードベース。 新しいコードがオプトインを開始します。
warnings オフ on 2 フェーズ移行: 最初に警告に対処し、後で注釈を付けます。
annotations on オフ 内部警告を修正する前に、パブリック API に注釈を付けます。

プロジェクト移行の目標に最も適した戦略を選択します。

  • 既定値として無効にします。 <Nullable>disable</Nullable>設定し、各ファイルを移行するときに各ファイルの先頭に#nullable enableを追加します。 既存のファイルは、変更を加えるまで null 許容コンテキストの対象外のままです。 このオプションは、安定したライブラリでは新機能の開発がまれであるため、導入の負担が最も少ない。
  • 既定値として有効にします。 <Nullable>enable</Nullable>設定し、まだ移行していないすべてのファイルの先頭に#nullable disableを追加します。 新しいファイルはすべて最初から null 許容に対応しているため、移行バックログは圧縮のみ可能です。 この選択は、開発がアクティブな場合に適しています。
  • 既定では警告を表示します。 <Nullable>warnings</Nullable>を設定します。 2 段階の移行では、この既定値を選択します。まず、すべての参照型が引き続き oblivious として扱われる状態で警告に対処し、その後で注釈を有効にします。 2 フェーズ分割では、各ステップの差分に焦点が合った状態が維持されます。
  • 既定の注釈。 <Nullable>annotations</Nullable>を設定します。 警告への対処を始める前に、まず ? を許可するメンバーに null を付けて、公開 API にアノテーションを追加します。 コンパイラはまだ警告を出力しないため、気を散らすことなく API サーフェスを解決できます。

プロジェクト ファイルによってグローバルの既定値が制御されます。 #nullable プリプロセッサ ディレクティブは、 コードの領域の既定値をオーバーライドします。

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

このディレクティブは、ソース ファイル内で、プロジェクトの null 許容設定の内外のリージョンを選択します。

#nullable disable
public static class LegacyHelper
{
    // This file is nullable-oblivious. Reference types use the legacy rules.
    public static string GetGreeting(string name) =>
        name == null ? "hello" : $"hello {name}";
}
#nullable restore

#nullable enable
public static class MigratedHelper
{
    // This file is fully migrated. Reference types are non-nullable by default.
    public static string GetGreeting(string? name) =>
        name is null ? "hello" : $"hello {name}";
}
#nullable restore

ファイルごとにファイルを移行する

大規模なプロジェクトを移行する最も予測可能な方法は、ファイルごとに警告または注釈ファイルを有効にすることです。 パターンは、選択した既定値に関係なく同じです。

  1. ファイルを選択します。 依存関係グラフの最も深いリーフ型から始めて、外側に移動します。 型に注釈を付けると、呼び出し元に新しい警告が発生するため、ボトムアップ作業によって再作業が最小限に抑えられます。
  2. ファイルを選択する #nullable ディレクティブを新しい動作に追加します。 両方のフラグが必要な場合は、 #nullable enable を使用します。 警告専用の #nullable enable warnings を使用します。
  3. Null 許容の警告を解決する」の手法を使用して、ファイル内の警告に対処してください。
  4. 次のファイルに対して繰り返します。
  5. プロジェクト内のすべてのファイルにディレクティブがある場合は、ディレクティブを削除し、プロジェクト レベルで <Nullable>enable</Nullable> 設定します。

コードベースに既に <Nullable>enable</Nullable>がある場合は、 反対 方向に進みます。 準備ができるまで、未移行のファイルでの警告を抑制します。 #nullable disableを使用してファイルをオプトアウトし、一度に 1 つずつ抑制を削除します。

2 つのフェーズで移行する

2 段階の移行では、null 許容参照型に伴う 2 種類の作業を切り分けます。 どの形式の安定性がより重要であるかに応じて、どちらの方法でもフェーズを順序付けることができます。

最初に警告を表示し、次に注釈を付けます

潜在的な System.NullReferenceException バグの修正を優先する場合は、警告を先に表示します。

  1. フェーズ 1: 警告に対処します。 プロジェクトの既定値を warnings に設定します。 参照型は引き続き null 許容性を意識しないままなので、型システムはまだ変わりません。 コンパイラは、既存のコードが既に System.NullReferenceExceptionをスローする可能性があるすべての場所で警告を出力します。 プロジェクトが警告クリーンになるまで、null チェックを追加したり、フローを再構築したり、属性を適用したりします。 各修正により、注釈が存在する前でも運用コードの回復性が向上します。
  2. フェーズ 2: 注釈を追加します。 プロジェクトの既定値を enableに切り替えます。 既定で、参照型は null 非許容になり、var ローカル変数は null 許容になります。 新しい警告には、変数の使用方法と一致しない宣言が反映されます。 ?を許可する型にnullを追加します。 null 以外の入力を必要とする API を強化します。

最初に注釈を付け、次に警告を表示する

パブリック API サーフェスを安定化するときの注釈を含むリードが優先されます。 このシーケンスはライブラリに適しています。注釈付き署名を出荷して、コンシューマーが適切なコントラクトを確認できるようにしてから、独自のスケジュールで内部警告を閉じます。

  1. フェーズ 1: 注釈を追加します。 プロジェクトの既定値を annotations に設定します。 参照型は既定では null 非許容になりますが、コンパイラは警告を出力しないため、ノイズは邪魔になりません。 パブリック API を確認し、?を正当に返したり受け入れたりできるすべてのメンバーにnullを追加します。 必要ではない署名を締めます。 警告がオフになっているため、実装を同時にアンタングルすることなく、フォーカスされたコミットで API の形状を解決できます。
  2. フェーズ 2: 警告に対処します。 プロジェクトの既定値を enableに切り替えます。 フェーズ 1 で追加した注釈は null 状態分析を提供するようになりました。そのため、コンパイラが出力する警告の品質は最初から高くなります。コードの各ポイントの動作が、既に発行したコントラクトと一致しません。 null 値許容警告を解決するの手法でそれらを解決します。

順序の選択

各順序により、フェーズがより小さく、より確認しやすい差分に分割されます。 1 つのフェーズは動作のみを変更し、もう 1 つは型のみを変更します。 欠点は、各ファイルに 2 回アクセスすることです。 すべての変更がリスクを伴う成熟した安定したコードの場合、通常、2 つのパスは価値があります。 実行中のコードを最も強化する場合は、 最初に警告 を選択します。 安定した契約を公開することを最も重視する場合は、まず 注釈 を選択します。

生成されたコードが除外される

コンパイラは、プロジェクトの設定に関係なく、null 許容コンテキストが無効になっているかのように、生成済みとしてマークされたファイルを扱います。 次のいずれかの条件に該当する場合、ファイルは生成されたと見なされます。

  • .editorconfig ルールは、ファイルの generated_code = true を設定します。
  • ファイル内の最初のコメントには、 <auto-generated> または <auto-generated/>が含まれています。
  • ファイル名は TemporaryGeneratedFile_で始まります。
  • ファイル名は、 .designer.cs.generated.cs.g.cs、または .g.i.csで終わります。

null 許容に対応した出力を生成するジェネレーターは、生成ファイルの先頭に #nullable enable を出力することで、これを再度有効にできます。

完了したら

すべてのファイルがプロジェクトの既定値に参加し、 <Nullable>enable</Nullable> 要素が設定された後:

  • ソース内のすべての #nullable ディレクティブを削除します。
  • 移行中に無音の警告にのみ追加した null! 初期化子と default! 初期化子を削除します。 それらを適切な初期化に置き換えるか、型を null 許容参照型にします。
  • パブリック API をスポットチェックします。 nullを取得または受け入れるすべてのメンバーには、?で注釈を付ける必要があります。 注釈は、パッケージが出荷されると契約の一部になります。

これで、新規プロジェクトと同じ状態になります。つまり、null 許容参照型が型システムの一部となり、新たに表示される警告は、宣言とコードの間に実際の不一致があることを示します。 警告が出たらその都度対処するには、null 許容警告を解決 を使用します。