NULL 値の許容について理解する

完了

あなたが .NET 開発者であれば、System.NullReferenceException に遭遇したことがあることでしょう。 これは、実行時に null が逆参照されているときに発生します。つまり、実行時に変数が評価されるときに、変数が null を参照しているときです。 この例外は、.NET エコシステム内で最も一般的に発生する例外です。 null の生みの親である Tony Hoare 卿は null を「10 億ドルの間違い」と呼んでいます。

次の例では、FooBar 変数に null が割り当てられ、すぐに逆参照されているために問題が発生しています。

// Declare variable and assign it as null.
FooBar fooBar = null;

// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();

// The FooBar type definition.
record FooBar(int Id, string Name);

アプリのサイズが大きくなり、より複雑になると、開発者としてこの問題を見つけるのはずっと困難になります。 このような潜在的エラーを見つけるのはツールの仕事であり、ここでは C# コンパイラが役立ちます。

Null Safety の定義

Null Safety という用語は、NullReferenceException が発生する可能性を減らすのに役立つ null 許容型に固有の一連の機能を定義します。

前の FooBar 例を考えた場合、逆参照する前に fooBar 変数が null かどうかを確認していれば、NullReferenceException を回避できました。

// Declare variable and assign it as null.
FooBar fooBar = null;

// Check for null
if (fooBar is not null)
{
    _ = fooBar.ToString();
}

// The FooBar type definition for example.
record FooBar(int Id, string Name);

このようなシナリオを特定するために、コンパイラではコードの意図を推測して、必要な動作を適用できます。 ただし、これは "Null 許容コンテキスト" が有効になっている場合のみになります。 Null 許容コンテキストについて説明する前に、使用可能な null 許容型について説明します。

null 許容型

C# 2.0 より前は、参照型だけが Null 許容でした。 int または DateTime などの値型は null にできません。 これらの型が値なしで初期化された場合は、default 値に戻されます。 int の場合、これは 0 です。 DateTime の場合は、DateTime.MinValue です。

初期値なしでインスタンス化された参照型の動作は異なります。 すべての参照型の default 値は null です。

次の C# スニペットについて考えてみましょう。

string first;                  // first is null
string second = string.Empty   // second is not null, instead it's an empty string ""
int third;                     // third is 0 because int is a value type
DateTime date;                 // date is DateTime.MinValue

前の例の場合:

  • firstnull です。これは、参照型 string が宣言されたが、割り当てが行われなかったためです。
  • second には、宣言時に string.Empty が割り当てられています。 オブジェクトには null 割り当てが行われたことはありません。
  • 割り当てられていないにもかかわらず、third0 です。 これは struct (値型) で、default 値は 0 です。
  • date は初期化されていませんが、その default 値は System.DateTime.MinValue です。

C# 2.0 以降、Nullable<T> (短縮形は T?) を使用して "Null 許容値型" を定義できるようになりました。 これにより、値型を Null 許容にできます。 次の C# スニペットについて考えてみましょう。

int? first;            // first is implicitly null (uninitialized)
int? second = null;    // second is explicitly null
int? third = default;  // third is null as the default value for Nullable<Int32> is null
int? fourth = new();    // fourth is 0, since new calls the nullable constructor

前の例の場合:

  • firstnull です。Null 許容の値型が初期化されていないためです。
  • second には、宣言時に null が割り当てられています。
  • Nullable<int>default 値は null のため、thirdnull です。
  • new() 式は Nullable<int> コンストラクターを呼び出し、int は既定で 0 のため、fourth0 です。

C# 8.0 では "Null 許容参照型" が導入されました。ここでは、参照型が null の "可能性あり"、または "常に" に null 以外である意図を表現できます。 あなたは、「すべての参照型は Null 許容であると思っていました」と思っているかもしれません。あなたは間違っていません。そのとおりです。 この機能を使用すると、あなたの "意図" を表現できます。そして、それをコンパイラーが適用しようとします。 同じ T? 構文が、参照型が Null 許容であることを意図されていることを表します。

次の C# スニペットについて考えてみましょう。

#nullable enable

string first = string.Empty;
string second;
string? third;

上記の例を考えた場合、コンパイラはあなたの "意図" を次のように推測します。

  • first は、確実に割り当てられているので、"決して" null ではない。
  • second は、最初は null だが、null である "はずがない"。 値を割り当てる前に second を評価すると、初期化されていないため、コンパイラ警告が発生します。
  • thirdnull の "可能性がある"。 たとえば、System.String を指している "可能性がある" が、null を指している "可能性もある"。 これらのバリエーションはどちらも受け入れ可能です。 null 値であることを最初に確認せずに third を逆参照すると、コンパイラは警告を出してあなたを助けてくれます。

重要

上記のように null 許容参照型機能を使用するには、それが "null 許容コンテキスト" 内にある必要があります。 詳細については、次のセクションで説明します。

Null 許容コンテキスト

null 許容コンテキストでは、コンパイラによる参照型変数の解釈方法を細かく制御できます。 次の 4 つの使用可能な Null 許容コンテキストがあります。

  • disable: コンパイラは、C# 7.3 以前と同様に動作します。
  • enable: コンパイラは、すべての null 参照分析とすべての言語機能を有効にします。
  • warnings: コンパイラは、すべての null 分析を実行し、コードが null を逆参照する可能性がある場合に警告を出します。
  • annotations: コンパイラでは null 分析は実行されず、コードが null を逆参照する可能性がある場合も警告は出されませんが、null 許容参照型 ? と null 免除演算子 (!) を使用してコードに注釈を付けることはできます。

このモジュールのスコープは、disable または enable Null 許容コンテキストに設定されています。 詳細については、「Null 許容参照型: Null 許容コンテキスト」を参照してください。

null 許容参照型を有効にする

C# プロジェクトファイル (.csproj) で、子 <Nullable> ノードを <Project> 要素に追加します (または、既存の <PropertyGroup> に追加します)。 これにより、enable Null 許容コンテキストがプロジェクト全体に適用されます。

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <!-- Omitted for brevity -->

</Project>

または、コンパイラ ディレクティブを使用して、"Null 許容コンテキスト" のスコープを C# ファイルに設定することもできます。

#nullable enable

上記の C# コンパイラ ディレクティブは、機能的にはプロジェクト構成と同等ですが、スコープはそれが存在するファイルに設定されています。 詳細については、「Null 許容参照型: Null 許容コンテキスト (ドキュメント)」を参照してください。

重要

.NET 6.0 以降のすべての C# プロジェクト テンプレートでは、Null 許容コンテキストは、既定で .csproj ファイルで有効になっています。

Null 許容コンテキストが有効になっている場合は、新しい警告が表示されます。 前の FooBar の例を考えてみましょう。Null 許容コンテキストで分析されたときに 2 つの警告が出されています。

  1. FooBar fooBar = null; 行には、null の割り当てに関する警告があります: C# Warning CS8600: Converting null literal or possible null value to non-nullable type (C# の警告 CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています)。

    C# の警告 CS8600 のスクリーンショット: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。

  2. _ = fooBar.ToString(); 行にも警告があります。 今回、コンパイラは、fooBar が Null 値かもしれないと心配しています: C# Warning CS8602: Dereference of a possibly null reference (C# の警告 CS8602: null 参照の可能性があるものの逆参照です)。

    C# の警告 CS8602 のスクリーンショット: null 参照の可能性があるものの逆参照です。

重要

すべての警告に対応して除去しても、Null Safety の "保証" はありません。 一部の限られたシナリオでは、コンパイラの分析をパスしてもランタイム NullReferenceException が発生するものがあります。

まとめ

このユニットでは、NullReferenceException を防ぐために C# で Null 許容コンテキストを有効にする方法について学習しました。 次のユニットでは、Null 許容コンテキストで意図を明示的に表現する方法について詳しく説明します。