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
前の例の場合:
first
はnull
です。これは、参照型string
が宣言されたが、割り当てが行われなかったためです。second
には、宣言時にstring.Empty
が割り当てられています。 オブジェクトにはnull
割り当てが行われたことはありません。- 割り当てられていないにもかかわらず、
third
は0
です。 これは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
前の例の場合:
first
はnull
です。Null 許容の値型が初期化されていないためです。second
には、宣言時にnull
が割り当てられています。Nullable<int>
のdefault
値はnull
のため、third
はnull
です。new()
式はNullable<int>
コンストラクターを呼び出し、int
は既定で0
のため、fourth
は0
です。
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
を評価すると、初期化されていないため、コンパイラ警告が発生します。third
はnull
の "可能性がある"。 たとえば、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 つの警告が出されています。
FooBar fooBar = null;
行には、null
の割り当てに関する警告があります: C# Warning CS8600: Converting null literal or possible null value to non-nullable type (C# の警告 CS8600: Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています)。_ = fooBar.ToString();
行にも警告があります。 今回、コンパイラは、fooBar
が Null 値かもしれないと心配しています: C# Warning CS8602: Dereference of a possibly null reference (C# の警告 CS8602: null 参照の可能性があるものの逆参照です)。
重要
すべての警告に対応して除去しても、Null Safety の "保証" はありません。 一部の限られたシナリオでは、コンパイラの分析をパスしてもランタイム NullReferenceException
が発生するものがあります。
まとめ
このユニットでは、NullReferenceException
を防ぐために C# で Null 許容コンテキストを有効にする方法について学習しました。 次のユニットでは、Null 許容コンテキストで意図を明示的に表現する方法について詳しく説明します。