やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#8.0 #nullableディレクティブにおける注釈・警告コンテキストについて

 動作確認。

成果物

nullコンテキスト

 nullコンテキストには以下4つの状態がありうる。

#nullable注釈警告
  • 注釈: annotations
  • 警告: warnings

 注釈と警告をそれぞれ個別に設定するには、以下のようなディレクティブを使う。それぞれの用途もあわせて記載する。

設定方法コンテキスト用途
#nullable注釈警告
enable annotationsnull対応を明確にコーディングせねば警告する
enable warnings参照型をnull許容型にしたら警告する
enablenull許容型と非許容型を別型として扱う。違反したら警告する
disableC#8.0以前と同じ

 無効にしたければenabledisableにする。また、プロジェクト設定に戻したければenablerestoreにする。

コード

EnableAnnotations.cs

 注釈と警告のうち、注釈だけを有効にしたコンテキストのコンパイル結果を確認する。

class EnableAnnotations
{
    public void Run()
    {
#nullable disable
        ;
#nullable enable annotations
        int i1 = null; // error CS0037: Null 非許容の値型であるため、Null を 'int' に変換できません
        int? i2 = null;
        string s1 = null;
        string? s2 = null;
        string s3 = null!;
        Console.WriteLine($"{i1}, {i2}, {s1}, {s2}, {s3}");
    }
}
EnableAnnotations.cs(12,22): error CS0037: Null 非許容の値型であるため、Null を 'int' に変換できません

EnableWarnings.cs

 注釈と警告のうち、警告だけを有効にしたコンテキストのコンパイル結果を確認する。

class EnableWarnings
{
    public void Run()
    {
#nullable disable
        ;
#nullable enable warnings
        int i1 = null; // error CS0037: Null 非許容の値型であるため、Null を 'int' に変換できません
        int? i2 = null;
        string s1 = null;
        string? s2 = null; // warning CS8632: '#nullable' 注釈コンテキスト内のコードでのみ、Null 許容参照型の注釈を使用する必要があります。
        string s3 = null!;
        Console.WriteLine($"{i1}, {i2}, {s1}, {s2}, {s3}");
    }
}
EnableWarnings.cs(12,22): error CS0037: Null 非許容の値型であるため、Null を 'int' に変換できません
EnableWarnings.cs(15,19): warning CS8632: '#nullable' 注釈コンテキスト内のコードでのみ、Null 許容参照型の注釈を使用する必要があります。

注釈: #nullable enable annotations

 注釈コンテキスト(annotations)が無効(disable)なら、C#8.0以前と同じ動作である。つまり以下。

  • null許容参照の宣言不可
  • 参照変数にnull代入可
  • 参照変数を逆参照しても警告なし
  • null免除演算子!使用不可

 注釈コンテキスト(annotations)が有効(enable)なら以下。

  • 参照型はすべてnull非許容参照である
  • null非許容参照は、安全に逆参照できる
  • null非許容参照は、nullを代入できる
    • 静的分析により逆参照時の値がnull以外と判明せねば警告する
      • このときnull免除演算子!を使って非nullであると宣言すれば警告を回避できる

 つまり、参照におけるnull対応を明確にコーディングするならenableにすべきだし、そうでないならdisableにすべき。

警告: #nullable enable warnings

 警告コンテキスト(warnings)が有効(enable)なら、参照がnullかもしれない場合に警告する。これは注釈コンテキストannotationsの有効(enable)/無効(disable)に関係なく行う。

 nullであるか否かはコンパイラによる静的分析で判断する。コンパイラが「非nullである」と確定できるのは以下2つのいずれかに該当する場合のみ。それ以外はすべて「nullかもしれない」と判断する。

  • 変数に null 以外の値が確実に割り当てられている。
  • 変数または式は、それを逆参照する前に null かどうかをチェックされている。

 警告する場合は以下2つ。

  • 「nullかもしれない」状態の変数が逆参照されたとき
  • 注釈コンテキストで「nullかもしれない」状態の変数や式を、null非許容参照型に代入したとき

コンパイラ解析の限界

 コンパイラによる静的分析には限界がある。たとえばif文などを用いたコードのロジックでnullになりえないとしても、それをコンパイラが静的分析で理解することは不可能である。そのときはプログラマがnull免除演算子!を付与して非nullであると示さねば警告が出たままとなる。

 ただし!は注釈コンテキスト(annotations)が有効(enable)でなければ使えない。

 と思っていたのだが、以下コードで警告が出ない。どのコンテキストでも。

string? s4 = (new Random().Next(0, 2) == 0) ? null : "A"; // nullかもしれない
Console.WriteLine($"{s4}");
Console.WriteLine($"{s4!}");
if (null != s4) { // nullでない
    Console.WriteLine($"{s4}"); // 逆参照。非nullであることが確実だから警告を消したい。
    Console.WriteLine($"{s4!}"); // 逆参照。非nullであることが確実だから警告を消したい。null免除演算子を使う
} else { // nullである
    Console.WriteLine($"{s4}"); // 逆参照。非nullであることが確実だから警告を消したい。
    Console.WriteLine($"{s4!}"); // 逆参照。非nullであることが確実だから警告を消したい。null免除演算子を使う
}

用途

 用途を予想する。

 警告コンテキストは、参照がnullかもしれない箇所を探すときに有意義なコンテキストである。既存コードのnull対応についてコードリーディングするときにnullかもしれない箇所をすぐ発見するのに役立つ。

 また、annotationsと併用すればnullかもしれない箇所に!を付与することで、非nullであることを明示できる。警告をすべて解消すれば、null安全なコードを書けた状態といえるはず。

ユースケース

 null安全なコードを書くときのユースケースについて。

既存コードをC#8.0null対応コードにしたい

  1. C#8.0以前の古い既存コードがある
  2. 1をC#8.0以降のnull対応コードにしたい
  3. まずは<Nullable>warnings</Nullable>で参照変数がnullかもしれない箇所を網羅する
    1. コンパイルして出た警告のコード箇所を網羅する
    2. コードを追いかけて関係するコード箇所を網羅する
    3. 関係するメソッドシグニチャなども含めて、どこをnull許容する・しないか決める
  4. <Nullable>enable</Nullable>でnull許容型を使えるようにする
    1. 3で決めた仕様どおりコード修正する
    2. コンパイルする
    3. 参照型変数が逆参照されている箇所において警告が出る
      1. コードを読んで逆参照コードより前の時点で非nullが確定していることを確認する
      2. 逆参照コードにnull免除演算子!をつける
      3. すべての警告箇所において非nullを確定させるコードに置換できたらnull安全と言える

既存コードはそのままでC#8.0null対応コードを追加したい

  1. 追加コードにおいて変数やメソッドがnull許容・非許容のいずれであるか設計する
  2. 新規追加コード箇所を#nullable enableディレクティブ指定する
  3. コーディングする
  4. コンパイルする
  5. 参照型変数が逆参照されている箇所において警告が出る
    1. コードを読んで逆参照コードより前の時点で非nullが確定していることを確認する
    2. 逆参照コードにnull免除演算子!をつける
    3. すべての警告箇所において非nullを確定させるコードに置換できたらnull安全と言える

C#8.0において新しいプロジェクトを作成する

  1. 変数やメソッドがnull許容・非許容のいずれであるか設計する
  2. <Nullable>enable</Nullable>でnull許容型を使えるようにする
  3. コーディングする
  4. コンパイルする
  5. 参照型変数が逆参照されている箇所において警告が出る
    1. コードを読んで逆参照コードより前の時点で非nullが確定していることを確認する
    2. 逆参照コードにnull免除演算子!をつける
    3. すべての警告箇所において非nullを確定させるコードに置換できたらnull安全と言える

ベストプラクティス

  • C#8.0において新規プロジェクト作成時は<Nullable>enable</Nullable>
  • 古い既存コードをnull対応に更新するなら<Nullable>warningenableとして確認・改修する
  • 古い既存コードはそのままにnull対応コードを新規追加するなら#nullable enable

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux