C#のnullとは?null許容型・nullチェック・演算子の使い方を初心者向けに徹底解説

はじめに

C#でプログラミングをしていると、必ずと言ってよいほど登場するのがnullです。

nullは「値がない」ことを表す重要な概念ですが、初心者にとっては少しつまずきやすいポイントでもあります。特に、NullReferenceExceptionというエラーに悩まされた経験がある人も多いでしょう。

C#では、参照型、値型、null許容型、null許容参照型、nullチェック、???.などの演算子を正しく理解することで、nullによるエラーを大きく減らせます。

この記事では、C#のnullについて、初心者にもわかりやすく基礎から実践的な使い方まで解説します。

1. C#のnullとは?初心者向けに意味をわかりやすく解説

1-1. nullは「値が存在しない状態」を表す特別な値

C#のnullとは、「値が存在しない」「何も参照していない」状態を表す特別な値です。

たとえば、次のように文字列型の変数にnullを代入できます。

C#
string name = null;

この場合、nameには文字列データが入っているわけではありません。つまり、"Taro"""のような文字列が存在しているのではなく、「まだ何も入っていない」「参照先がない」という状態です。

C#では、オブジェクトを扱うときにnullがよく使われます。

C#
User user = null;

このコードは、userという変数は存在しているものの、実際のUserオブジェクトはまだ代入されていない状態を表します。

1-2. nullと0・空文字・falseの違い

nullは、0、空文字、falseとはまったく異なります。

C#
int number = 0;
string emptyText = "";
bool flag = false;
string text = null;

それぞれの意味は次のとおりです。

意味
0数値としてのゼロ
""長さ0の文字列
false真偽値の偽
null値そのものが存在しない状態

たとえば、空文字""は「文字数が0の文字列」という値が存在しています。一方で、nullは文字列オブジェクト自体が存在していません。

C#
string a = "";
string b = null;

Console.WriteLine(a.Length); // 0
Console.WriteLine(b.Length); // NullReferenceException

aは空文字なのでLengthを取得できますが、bnullなのでLengthを呼び出そうとするとエラーになります。

1-3. C#でnullが使われる主な場面

C#でnullが使われる主な場面には、次のようなものがあります。

C#
string name = null;
User user = null;
DateTime? birthday = null;

たとえば、データベースからユーザー情報を取得する処理で、該当するユーザーが見つからなかった場合にnullを返すことがあります。

C#
User? FindUser(int id)
{
if (id == 1)
{
return new User { Name = "Taro" };
}

return null;
}

また、入力フォームで「誕生日が未入力」のような状態を表すために、DateTime?のようなnull許容型を使うこともあります。

C#
DateTime? birthday = null;

このように、C#のnullは「まだ値が決まっていない」「対象が存在しない」「未入力である」といった状態を表すために使われます。

1-4. nullを理解しないと起こりやすいエラー

C#でnullを理解せずにコードを書くと、代表的なエラーであるNullReferenceExceptionが発生しやすくなります。

C#
string name = null;
Console.WriteLine(name.Length);

このコードでは、namenullなのにLengthプロパティにアクセスしているため、実行時にエラーになります。

NullReferenceExceptionは、C#初心者がよく遭遇するエラーの一つです。原因は、「nullかもしれない変数に対して、メソッドやプロパティを呼び出していること」です。

安全に扱うには、事前にnullチェックを行います。

C#
string name = null;

if (name != null)
{
Console.WriteLine(name.Length);
}

このように、C#でnullを正しく理解することは、エラーの少ない安全なプログラムを書くために欠かせません。

2. C#でnullを扱える型と扱えない型

2-1. 参照型はnullを代入できる

C#には、大きく分けて「参照型」と「値型」があります。

参照型は、オブジェクトの実体そのものではなく、オブジェクトへの参照を保持する型です。参照先がない状態を表すために、nullを代入できます。

代表的な参照型には、次のようなものがあります。

C#
string name = null;
object obj = null;
User user = null;
int[] numbers = null;
List<string> list = null;

classで定義した型も参照型です。

C#
class User
{
public string Name { get; set; }
}

User user = null;

この場合、userUser型の変数ですが、実際のUserオブジェクトは参照していません。

2-2. 値型はそのままではnullを代入できない

一方で、値型には基本的にnullを代入できません。

代表的な値型には、次のようなものがあります。

C#
int number = 10;
bool isActive = true;
DateTime today = DateTime.Now;
double price = 100.5;

次のようにintnullを代入しようとすると、コンパイルエラーになります。

C#
int number = null; // エラー

値型は、変数そのものが値を持つ型です。そのため、通常は必ず何らかの値を持つ必要があります。

ただし、C#には値型でもnullを扱えるようにする「null許容型」という仕組みがあります。

C#
int? number = null;
DateTime? birthday = null;

この?を付けた書き方については、後ほど詳しく解説します。

2-3. stringは参照型なのでnullになり得る

C#のstringはよく使われる型ですが、実は参照型です。そのため、nullを代入できます。

C#
string message = null;

ただし、stringは特別扱いされることも多いため、値型のように感じる人もいるかもしれません。

次の3つはすべて異なる状態です。

C#
string a = null;
string b = "";
string c = "Hello";

aは値が存在しない状態、bは空の文字列、cHelloという文字列です。

文字列を扱うときは、nullと空文字の違いを意識することが重要です。

C#
string text = null;

if (text == null)
{
Console.WriteLine("textはnullです");
}

文字列が未設定の可能性がある場合は、string.IsNullOrEmptystring.IsNullOrWhiteSpaceを使うと安全です。

2-4. int・bool・DateTimeなどの値型とnullの関係

intboolDateTimeなどの値型は、そのままではnullを持てません。

C#
int age = null;          // エラー
bool isActive = null; // エラー
DateTime date = null; // エラー

しかし、現実のプログラムでは「年齢が未入力」「有効かどうか未設定」「日付が未定」といった状態を表したいことがあります。

そのような場合は、null許容型を使います。

C#
int? age = null;
bool? isActive = null;
DateTime? date = null;

int?は「int型の値、またはnull」を表します。bool?truefalsenullの3つの状態を持てます。

C#
bool? isActive = null;

if (isActive == null)
{
Console.WriteLine("未設定です");
}

このように、値型でnullを扱いたい場合は、?を付けたnull許容型を使います。

3. null許容型とは?Nullable型の使い方

3-1. null許容型は値型にnullを許可する仕組み

null許容型とは、通常はnullを代入できない値型に対して、nullを許可するための仕組みです。

たとえば、通常のintにはnullを代入できません。

C#
int score = null; // エラー

しかし、int?と書くことで、intの値またはnullを代入できるようになります。

C#
int? score = null;
score = 100;

これは、入力が任意の項目や、データベース上でNULLを許可している列を扱うときによく使われます。

C#
DateTime? deletedAt = null;

この例では、deletedAtnullなら「まだ削除されていない」、日付が入っていれば「削除済み」と判断できます。

3-2. int?・bool?・DateTime?の書き方

null許容型は、値型の後ろに?を付けて書きます。

C#
int? age = null;
bool? isMember = null;
DateTime? birthday = null;
double? price = null;

それぞれ次のような意味になります。

C#
int? age = null;             // 年齢が未入力
bool? isMember = null; // 会員かどうか未設定
DateTime? birthday = null; // 誕生日が未入力

値が入っている場合は、通常の値型と同じように代入できます。

C#
age = 20;
isMember = true;
birthday = new DateTime(2000, 1, 1);

nullかどうかを判定するには、通常の== nullを使えます。

C#
if (age == null)
{
Console.WriteLine("年齢は未入力です");
}

また、値が入っているかどうかはHasValueでも確認できます。

C#
if (age.HasValue)
{
Console.WriteLine(age.Value);
}

3-3. Nullable<T>と「?」の関係

int?という書き方は、実はNullable<int>の省略形です。

C#
int? a = null;
Nullable<int> b = null;

この2つは同じ意味です。

つまり、次のように対応しています。

省略形正式な型
int?Nullable<int>
bool?Nullable<bool>
DateTime?Nullable<DateTime>
double?Nullable<double>

通常は、簡潔に書けるint?DateTime?を使うことが多いです。

C#
DateTime? startDate = null;

ただし、仕組みとしてはNullable<T>という構造体が使われていることを理解しておくと、HasValueValueの意味がわかりやすくなります。

3-4. HasValueとValueでnullを判定・取得する方法

null許容型には、HasValueValueというプロパティがあります。

C#
int? number = 10;

if (number.HasValue)
{
Console.WriteLine(number.Value);
}

HasValueは、値が入っていればtruenullならfalseを返します。

Valueは、実際の値を取り出すために使います。

C#
int? number = 10;

Console.WriteLine(number.HasValue); // True
Console.WriteLine(number.Value); // 10

ただし、nullの状態でValueにアクセスすると例外が発生します。

C#
int? number = null;

Console.WriteLine(number.Value); // InvalidOperationException

そのため、Valueを使う前には必ずHasValueで確認しましょう。

C#
int? number = null;

if (number.HasValue)
{
Console.WriteLine(number.Value);
}
else
{
Console.WriteLine("値がありません");
}

最近のC#では、??演算子を使ってデフォルト値を指定する書き方もよく使われます。

C#
int? number = null;
int result = number ?? 0;

Console.WriteLine(result); // 0

3-5. null許容型を使うべきケース

null許容型は、「値がない状態」に意味がある場合に使います。

たとえば、次のようなケースです。

C#
int? age = null;
DateTime? birthday = null;
bool? isApproved = null;

年齢が0の場合と未入力の場合は意味が違います。誕生日も、DateTime.MinValueを仮の値として入れるより、未入力ならnullにしたほうが自然です。

C#
DateTime? birthday = null;

if (birthday == null)
{
Console.WriteLine("誕生日は未入力です");
}

一方で、必ず値が存在する項目にはnull許容型を使う必要はありません。

C#
int quantity = 0;
bool isEnabled = false;

「値がない状態」を業務上表現したい場合はnull許容型を使い、常に値があるべき場合は通常の値型を使う、という考え方が基本です。

4. null許容参照型とは?C# 8.0以降のnull対策

4-1. null許容参照型が導入された理由

C# 8.0以降では、null許容参照型という機能が導入されました。

従来のC#では、参照型であるstringやクラス型には当然のようにnullを代入できました。

C#
string name = null;

しかし、この仕組みは便利な一方で、NullReferenceExceptionを引き起こしやすいという問題がありました。

そこで導入されたのが、null許容参照型です。これは、「nullにならない想定の参照型」と「nullになる可能性がある参照型」をコード上で区別できる仕組みです。

C#
string name = "Taro";    // nullを想定しない
string? nickname = null; // nullを許可する

これにより、コンパイラがnullの危険性を警告してくれるようになります。

4-2. stringとstring?の違い

null許容参照型が有効な場合、stringstring?には明確な違いがあります。

C#
string name = "Taro";
string? nickname = null;

stringは、基本的にnullを入れない前提の型です。

C#
string name = null; // 警告

一方で、string?はnullを許可する型です。

C#
string? nickname = null; // OK

string?を使うと、その変数を利用する前にnullチェックをするようコンパイラが促してくれます。

C#
string? nickname = GetNickname();

Console.WriteLine(nickname.Length); // 警告が出る可能性がある

if (nickname != null)
{
Console.WriteLine(nickname.Length); // 安全
}

重要なのは、string?は実行時に特別な別型になるわけではないという点です。あくまでコンパイラによるnullチェックを強化するための仕組みです。

4-3. nullable enableの意味と設定方法

null許容参照型を使うには、Nullableコンテキストを有効にする必要があります。

プロジェクト全体で有効にする場合は、.csprojファイルに次のように設定します。

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

これにより、プロジェクト内でnull許容参照型の警告が有効になります。

ファイル単位で有効にしたい場合は、C#ファイルの先頭に次のように書きます。

C#
#nullable enable

逆に無効にしたい場合は、次のように書きます。

C#
#nullable disable

新しいC#プロジェクトでは、最初から<Nullable>enable</Nullable>が設定されていることもあります。

null関連の警告が表示される場合は、まずプロジェクトのNullable設定を確認しましょう。

4-4. コンパイラ警告でnull参照を防ぐ仕組み

null許容参照型を有効にすると、コンパイラが「この変数はnullかもしれない」と判断した箇所で警告を出します。

C#
string? name = null;

Console.WriteLine(name.Length); // 警告

この警告は、実行時エラーを事前に防ぐための重要なサインです。

安全に書くには、nullチェックを行います。

C#
string? name = null;

if (name != null)
{
Console.WriteLine(name.Length);
}

また、??演算子を使ってデフォルト値を設定する方法もあります。

C#
string? name = null;
string displayName = name ?? "ゲスト";

Console.WriteLine(displayName);

このように、null許容参照型は、C#のnullによるバグをコンパイル時点で見つけやすくするための機能です。

4-5. null許容参照型を使うときの注意点

null許容参照型を使うときは、?を付ければ何でも安全になるわけではありません。

C#
string? name = null;
Console.WriteLine(name.Length); // 危険

string?は「nullの可能性がある」と宣言しているだけなので、使う前にはnullチェックが必要です。

また、既存のライブラリや古いコードでは、null許容参照型の情報が十分に付いていない場合があります。そのため、コンパイラの警告が完全ではないケースもあります。

さらに、null免除演算子!を多用すると、せっかくのnullチェックの意味が薄れてしまいます。

C#
string? name = null;
Console.WriteLine(name!.Length); // 警告は消えるが実行時エラーになる

null許容参照型は便利ですが、基本は「nullになる可能性を明確にし、必要な場所でチェックする」ことです。

5. C#のnullチェック方法

5-1. if文でnullをチェックする基本

C#でnullを安全に扱う基本は、if文によるnullチェックです。

C#
string? name = GetName();

if (name != null)
{
Console.WriteLine(name.Length);
}

このように、変数がnullでないことを確認してからメソッドやプロパティを使います。

逆に、nullの場合の処理を書きたいときは次のようにします。

C#
if (name == null)
{
Console.WriteLine("名前が設定されていません");
}

nullチェックは地味ですが、NullReferenceExceptionを防ぐための最も基本的な方法です。

5-2. == nullと!= nullの使い方

C#では、== null!= nullを使ってnullかどうかを判定できます。

C#
string? text = null;

if (text == null)
{
Console.WriteLine("textはnullです");
}

if (text != null)
{
Console.WriteLine(text.Length);
}

== nullは「nullである場合」、!= nullは「nullでない場合」を意味します。

オブジェクトでも同じように使えます。

C#
User? user = GetUser();

if (user != null)
{
Console.WriteLine(user.Name);
}

初心者はまず、この== null!= nullを覚えておくとよいでしょう。

5-3. is nullとis not nullの使い方

C#では、パターンマッチングを使ってis nullis not nullと書くこともできます。

C#
string? name = GetName();

if (name is null)
{
Console.WriteLine("nameはnullです");
}

if (name is not null)
{
Console.WriteLine(name.Length);
}

is null== nullと似ていますが、パターンマッチングの一部として使えるため、より明確で読みやすいと感じる人もいます。

特に、is not nullは自然な英語に近い形で読めます。

C#
if (user is not null)
{
Console.WriteLine(user.Name);
}

基本的には、== nullis nullのどちらを使っても構いません。チームのコーディングルールや好みに合わせて統一するとよいでしょう。

5-4. string.IsNullOrEmptyで文字列をチェックする

文字列のnullチェックでは、string.IsNullOrEmptyがよく使われます。

C#
string? name = "";

if (string.IsNullOrEmpty(name))
{
Console.WriteLine("名前が未入力です");
}

string.IsNullOrEmptyは、対象の文字列がnullまたは空文字""の場合にtrueを返します。

つまり、次の2つをまとめて判定できます。

C#
name == null || name == ""

たとえば、フォーム入力で名前が未入力かどうかを調べる場合に便利です。

C#
if (string.IsNullOrEmpty(inputName))
{
Console.WriteLine("名前を入力してください");
}

ただし、スペースだけの文字列は空文字とはみなされません。

C#
string text = "   ";

Console.WriteLine(string.IsNullOrEmpty(text)); // False

スペースだけの入力も未入力として扱いたい場合は、string.IsNullOrWhiteSpaceを使います。

5-5. string.IsNullOrWhiteSpaceとの違い

string.IsNullOrWhiteSpaceは、文字列がnull、空文字、または空白文字だけの場合にtrueを返します。

C#
string? text1 = null;
string text2 = "";
string text3 = " ";

Console.WriteLine(string.IsNullOrWhiteSpace(text1)); // True
Console.WriteLine(string.IsNullOrWhiteSpace(text2)); // True
Console.WriteLine(string.IsNullOrWhiteSpace(text3)); // True

string.IsNullOrEmptyとの違いは、空白だけの文字列をどう扱うかです。

C#
string text = "   ";

Console.WriteLine(string.IsNullOrEmpty(text)); // False
Console.WriteLine(string.IsNullOrWhiteSpace(text)); // True

ユーザー入力のチェックでは、空白だけの入力も無効にしたいことが多いため、string.IsNullOrWhiteSpaceのほうが適している場合があります。

C#
if (string.IsNullOrWhiteSpace(userName))
{
Console.WriteLine("ユーザー名を入力してください");
}

文字列のnullチェックでは、用途に応じてこの2つを使い分けましょう。

5-6. nullチェックを忘れたときに起こるNullReferenceException

nullチェックを忘れて、nullの変数に対してプロパティやメソッドを呼び出すと、NullReferenceExceptionが発生します。

C#
User? user = null;

Console.WriteLine(user.Name); // NullReferenceException

この例では、usernullなのにNameにアクセスしているため、実行時エラーになります。

安全に書くには、事前にnullチェックを行います。

C#
if (user != null)
{
Console.WriteLine(user.Name);
}

また、null条件演算子?.を使う方法もあります。

C#
Console.WriteLine(user?.Name);

usernullの場合、user?.Nameはエラーにならずnullを返します。

C#でnullを扱うときは、「この変数はnullになる可能性があるか」を常に意識することが大切です。

6. C#でnullを扱う演算子の使い方

6-1. null合体演算子「??」の使い方

null合体演算子??は、左辺がnullでない場合は左辺の値を返し、nullの場合は右辺の値を返す演算子です。

C#
string? name = null;
string displayName = name ?? "ゲスト";

Console.WriteLine(displayName); // ゲスト

このコードでは、namenullなので、代わりに"ゲスト"が使われます。

??を使わずに書くと、次のようになります。

C#
string displayName;

if (name != null)
{
displayName = name;
}
else
{
displayName = "ゲスト";
}

??を使うと、nullの場合のデフォルト値を簡潔に書けます。

C#
int? score = null;
int result = score ?? 0;

Console.WriteLine(result); // 0

null許容型でも参照型でもよく使われる便利な演算子です。

6-2. null合体代入演算子「??=」の使い方

null合体代入演算子??=は、左辺がnullの場合だけ右辺の値を代入する演算子です。

C#
string? name = null;

name ??= "ゲスト";

Console.WriteLine(name); // ゲスト

もし左辺がすでに値を持っている場合は、代入されません。

C#
string? name = "Taro";

name ??= "ゲスト";

Console.WriteLine(name); // Taro

??=を使わずに書くと、次のようになります。

C#
if (name == null)
{
name = "ゲスト";
}

リストや設定値の初期化でも便利です。

C#
List<string>? names = null;

names ??= new List<string>();
names.Add("Taro");

??=は、「まだ値がなければ初期化する」という場面でよく使われます。

6-3. null条件演算子「?.」の使い方

null条件演算子?.は、左辺がnullでない場合だけ、メソッドやプロパティにアクセスする演算子です。

C#
User? user = null;

Console.WriteLine(user?.Name);

この場合、usernullなので、user?.Nameはエラーにならずnullを返します。

通常の.を使うと、NullReferenceExceptionになります。

C#
Console.WriteLine(user.Name); // 危険

?.は、オブジェクトがnullかもしれない場合に安全にアクセスしたいときに使います。

C#
User? user = GetUser();

int? nameLength = user?.Name?.Length;

このコードでは、usernullでも、Namenullでも、エラーにならずnullになります。

6-4. null条件演算子「?[]」の使い方

配列やリストの要素にアクセスするときは、null条件演算子?[]を使えます。

C#
int[]? numbers = null;

int? first = numbers?[0];

この場合、numbersnullなら、numbers?[0]はエラーにならずnullを返します。

配列が存在する場合は、通常どおり要素を取得します。

C#
int[]? numbers = new[] { 10, 20, 30 };

int? first = numbers?[0];

Console.WriteLine(first); // 10

ただし、?[]は配列やリスト自体がnullかどうかをチェックするだけです。インデックスの範囲外アクセスまでは防げません。

C#
int[]? numbers = new[] { 10 };

int? value = numbers?[5]; // IndexOutOfRangeException

つまり、?[]はnull対策にはなりますが、範囲チェックは別途必要です。

6-5. null許容演算子と三項演算子の使い分け

C#でnull対策をするときは、???.などのnull関連演算子と、三項演算子?:を使い分けます。

??は、nullの場合の代替値を指定したいときに便利です。

C#
string? name = null;
string displayName = name ?? "ゲスト";

三項演算子は、条件によって返す値を変えたいときに使います。

C#
string? name = null;
string displayName = name != null ? name : "ゲスト";

この例では、??のほうが簡潔です。

一方で、null以外の条件も含めて分岐したい場合は三項演算子が向いています。

C#
int score = 80;
string result = score >= 60 ? "合格" : "不合格";

nullチェックだけなら???.、複雑な条件分岐なら三項演算子、というように使い分けると読みやすいコードになります。

6-6. null免除演算子「!」の意味と注意点

null免除演算子!は、コンパイラに対して「これはnullではないとみなしてよい」と伝えるための演算子です。

C#
string? name = GetName();

Console.WriteLine(name!.Length);

このコードでは、namestring?でnullの可能性があっても、!によってコンパイラ警告を抑制しています。

ただし、!は実行時にnullチェックをしてくれるわけではありません。

C#
string? name = null;

Console.WriteLine(name!.Length); // NullReferenceException

このように、実際にnullだった場合はエラーになります。

null免除演算子は、コンパイラには判断できないが、開発者が確実にnullではないと保証できる場合にだけ使うべきです。

たとえば、フレームワークによって後から値が設定されるプロパティなどで使われることがあります。

C#
public string Name { get; set; } = null!;

便利な演算子ですが、多用するとnull許容参照型のメリットが薄れるため、必要最小限にしましょう。

7. C#のnullでよくあるエラーと対処法

7-1. NullReferenceExceptionの原因

NullReferenceExceptionは、C#でnullに関連して最もよく発生するエラーです。

原因は、nullの変数に対してメソッドやプロパティを呼び出すことです。

C#
string? name = null;

Console.WriteLine(name.Length); // NullReferenceException

クラスのプロパティにアクセスするときにも発生します。

C#
User? user = null;

Console.WriteLine(user.Name); // NullReferenceException

対処法は、nullチェックを行うことです。

C#
if (user != null)
{
Console.WriteLine(user.Name);
}

または、null条件演算子を使います。

C#
Console.WriteLine(user?.Name);

さらに、nullの場合のデフォルト値を指定したいなら??を使います。

C#
string displayName = user?.Name ?? "ゲスト";

NullReferenceExceptionが発生したときは、エラー行で使っている変数のどれがnullなのかを確認することが大切です。

7-2. 「nullを非null型に変換できない」警告への対応

null許容参照型を有効にしていると、次のような警告が出ることがあります。

C#
string name = null; // 警告

これは、stringがnullを想定しない型なのに、nullを代入しているためです。

対応方法は主に3つあります。

1つ目は、nullを許可するならstring?に変更する方法です。

C#
string? name = null;

2つ目は、初期値を設定してnullを避ける方法です。

C#
string name = "";

3つ目は、nullにならないように設計を見直す方法です。

C#
public User(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}

public string Name { get; }

警告を安易に!で消すのではなく、本当にnullを許可すべきかを考えることが重要です。

7-3. null許容型のValueで例外が発生するケース

null許容型でよくあるエラーが、Valueプロパティの使い方です。

C#
int? age = null;

Console.WriteLine(age.Value); // InvalidOperationException

agenullの状態でValueを呼び出すと、例外が発生します。

安全に取得するには、HasValueで確認します。

C#
if (age.HasValue)
{
Console.WriteLine(age.Value);
}
else
{
Console.WriteLine("年齢は未入力です");
}

または、??を使ってデフォルト値を指定します。

C#
int displayAge = age ?? 0;

単純に値を取り出したい場合は、GetValueOrDefaultも使えます。

C#
int displayAge = age.GetValueOrDefault();

GetValueOrDefaultは、値があればその値を返し、nullなら型の既定値を返します。int?なら0bool?ならfalseになります。

C#
int? count = null;
Console.WriteLine(count.GetValueOrDefault()); // 0

7-4. メソッドの戻り値がnullになる場合の対策

メソッドの戻り値がnullになる可能性がある場合は、戻り値の型に?を付けると意図が明確になります。

C#
User? FindUser(int id)
{
if (id == 1)
{
return new User { Name = "Taro" };
}

return null;
}

呼び出し側では、nullチェックを行います。

C#
User? user = FindUser(2);

if (user == null)
{
Console.WriteLine("ユーザーが見つかりません");
}
else
{
Console.WriteLine(user.Name);
}

また、nullの場合の代替値を用意することもできます。

C#
string displayName = FindUser(2)?.Name ?? "不明";

メソッドがnullを返す可能性があるなら、そのことを型やメソッド名、コメントなどで明確にすることが大切です。

C#
User? FindUserOrNull(int id)
{
return null;
}

ただし、場合によってはnullを返すより、例外を投げる、空リストを返す、結果型を使うといった設計のほうが適していることもあります。

7-5. LINQや配列・Listでnullが発生するケース

LINQや配列、Listを使うときにもnullには注意が必要です。

たとえば、FirstOrDefaultは条件に一致する要素がない場合、参照型ではnullを返すことがあります。

C#
List<User> users = new List<User>();

User? user = users.FirstOrDefault(u => u.Name == "Taro");

Console.WriteLine(user.Name); // NullReferenceExceptionの可能性

安全に書くには、nullチェックを行います。

C#
if (user != null)
{
Console.WriteLine(user.Name);
}

また、リスト自体がnullの場合にも注意が必要です。

C#
List<string>? names = null;

Console.WriteLine(names.Count); // NullReferenceException

この場合は、null条件演算子や??を使えます。

C#
int count = names?.Count ?? 0;

配列でも同様です。

C#
string[]? items = null;

string? first = items?[0];

ただし、配列が空の場合にitems?[0]を使うと、範囲外アクセスの例外が発生します。nullだけでなく、要素数のチェックも忘れないようにしましょう。

C#
if (items != null && items.Length > 0)
{
Console.WriteLine(items[0]);
}

8. C#でnullを安全に扱う実践パターン

8-1. メソッド引数でnullを受け取らない設計

nullによるエラーを減らすには、そもそもnullを受け取らない設計にすることが大切です。

たとえば、名前を表示するメソッドで、nameがnullだと困るなら、引数を非nullとして扱います。

C#
void PrintName(string name)
{
Console.WriteLine(name);
}

null許容参照型が有効な場合、stringはnullを想定しない型として扱われます。

呼び出し側でnullの可能性がある場合は、事前にチェックします。

C#
string? name = GetName();

if (name != null)
{
PrintName(name);
}

または、デフォルト値を渡します。

C#
PrintName(name ?? "ゲスト");

メソッド側でnullを許可する必要がないなら、string?ではなくstringを使い、nullを設計上避けることが重要です。

8-2. ArgumentNullExceptionで早期に検出する

メソッドやコンストラクタの引数にnullが渡されると困る場合は、早い段階でArgumentNullExceptionを投げるとよいです。

C#
void RegisterUser(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

Console.WriteLine($"登録: {name}");
}

C#では、次のように簡潔に書くこともできます。

C#
void RegisterUser(string name)
{
ArgumentNullException.ThrowIfNull(name);

Console.WriteLine($"登録: {name}");
}

コンストラクタでもよく使われます。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

nullが不正な値である場合は、後の処理でNullReferenceExceptionになるよりも、入口で明確な例外を出したほうが原因を特定しやすくなります。

8-3. デフォルト値を設定してnullを避ける

nullを許可する必要がない場合は、最初からデフォルト値を設定しておくと安全です。

C#
string name = "";
List<string> items = new List<string>();

クラスのプロパティでも、初期値を設定しておくとnullを避けやすくなります。

C#
class User
{
public string Name { get; set; } = "";
public List<string> Roles { get; set; } = new List<string>();
}

これにより、次のようなコードでnullチェックを減らせます。

C#
User user = new User();

Console.WriteLine(user.Name.Length);
Console.WriteLine(user.Roles.Count);

また、読み取り専用の空配列を使うこともあります。

C#
string[] names = Array.Empty<string>();

nullを使わなくても「データがない」ことを表現できる場合は、空文字、空配列、空リストなどを活用するとコードがシンプルになります。

8-4. nullを返すより空配列・空リストを返すべきケース

複数件のデータを返すメソッドでは、nullではなく空配列や空リストを返すほうが扱いやすいことが多いです。

悪い例です。

C#
List<string>? GetNames()
{
return null;
}

この場合、呼び出し側は毎回nullチェックが必要になります。

C#
List<string>? names = GetNames();

if (names != null)
{
foreach (var name in names)
{
Console.WriteLine(name);
}
}

空リストを返すようにすると、呼び出し側がシンプルになります。

C#
List<string> GetNames()
{
return new List<string>();
}

呼び出し側では、そのままループできます。

C#
foreach (var name in GetNames())
{
Console.WriteLine(name);
}

配列の場合は、Array.Empty<T>()を使うと効率的です。

C#
string[] GetNames()
{
return Array.Empty<string>();
}

「データが0件」と「取得できなかった」を区別する必要がない場合は、nullではなく空のコレクションを返す設計がおすすめです。

8-5. nullチェックを減らす設計の考え方

nullチェックを完全になくすことは難しいですが、設計によって大きく減らすことはできます。

基本的な考え方は、次のとおりです。

方針内容
nullを許可する場所を限定するどこでもnullを許可しない
必須項目は非nullにするstringや通常の値型を使う
未入力に意味がある場合だけnullを使うstring?int?を使う
コレクションは空で返すnullではなく空リストを返す
入口でチェックするArgumentNullExceptionで早期検出する

たとえば、次のようにプロパティを初期化しておくと、利用側でnullチェックを減らせます。

C#
class Order
{
public List<OrderItem> Items { get; } = new List<OrderItem>();
}

呼び出し側では、安心してItemsを使えます。

C#
Order order = new Order();

Console.WriteLine(order.Items.Count);

C#のnull対策では、単にif文を増やすのではなく、「nullになりにくい設計」を意識することが重要です。

9. C#のnullに関するよくある質問

9-1. C#のnullとdefaultの違いは?

nullは、参照先がない状態を表す値です。一方、defaultは型ごとの既定値を表します。

C#
string s = default; // null
int n = default; // 0
bool b = default; // false

参照型のdefaultは基本的にnullです。

C#
object obj = default; // null

値型のdefaultは、その型の初期値になります。

C#
int number = default;       // 0
bool flag = default; // false
DateTime date = default; // 0001/01/01 00:00:00

null許容型のdefaultnullです。

C#
int? age = default; // null

つまり、nullは「値がない状態」を直接表し、defaultは「その型の既定値」を表します。型によってdefaultの結果が変わる点に注意しましょう。

9-2. nullとDBNull.Valueの違いは?

nullDBNull.Valueは似ているようで異なります。

nullは、C#上で「値が存在しない」ことを表します。

C#
string? name = null;

一方、DBNull.Valueは、主にデータベースのNULL値を表すために使われる特殊な値です。

C#
object dbValue = DBNull.Value;

たとえば、データベースから取得した値がNULLの場合、DBNull.Valueとして返されることがあります。

C#
if (dbValue == DBNull.Value)
{
Console.WriteLine("データベース上のNULLです");
}

DBNull.Valuenullではないため、次の判定は別物です。

C#
dbValue == null
dbValue == DBNull.Value

データベース処理では、C#のnullとデータベースのNULLを変換する処理が必要になることがあります。

9-3. null許容型はいつ使うべき?

null許容型は、「値がない状態」に意味がある場合に使うべきです。

たとえば、次のような項目です。

C#
int? age = null;
DateTime? birthday = null;
bool? isApproved = null;

年齢が0なのか未入力なのかを区別したい場合、int?が適しています。

C#
int? age = null;

if (age == null)
{
Console.WriteLine("年齢は未入力です");
}

日付でも、DateTime.MinValueを仮の値として使うより、未設定ならnullを使ったほうが意味が明確です。

C#
DateTime? completedAt = null;

一方で、必ず値が存在する項目にはnull許容型を使う必要はありません。

C#
int quantity = 0;
bool isEnabled = false;

「未設定」「不明」「該当なし」を表したいときにnull許容型を使う、と覚えておくとよいでしょう。

9-4. null免除演算子「!」は使ってもよい?

null免除演算子!は使ってもよいですが、慎重に使う必要があります。

C#
string? name = GetName();

Console.WriteLine(name!.Length);

!は、コンパイラのnull警告を抑制するだけです。実行時にnullチェックをしてくれるわけではありません。

そのため、実際にnullだった場合はエラーになります。

C#
string? name = null;

Console.WriteLine(name!.Length); // NullReferenceException

基本的には、!を使う前にnullチェックや設計の見直しを検討しましょう。

C#
if (name != null)
{
Console.WriteLine(name.Length);
}

どうしてもコンパイラが判断できないが、開発者が確実にnullではないと保証できる場合にだけ使うのが安全です。

9-5. 初心者がまず覚えるべきnull対策は?

初心者がまず覚えるべきC#のnull対策は、次の5つです。

1つ目は、nullは「値がない状態」だと理解することです。

C#
string? name = null;

2つ目は、nullかもしれない変数を使う前にチェックすることです。

C#
if (name != null)
{
Console.WriteLine(name.Length);
}

3つ目は、文字列ではstring.IsNullOrEmptystring.IsNullOrWhiteSpaceを使うことです。

C#
if (string.IsNullOrWhiteSpace(name))
{
Console.WriteLine("名前が未入力です");
}

4つ目は、nullの場合のデフォルト値には??を使うことです。

C#
string displayName = name ?? "ゲスト";

5つ目は、nullの可能性があるオブジェクトには?.を使うことです。

C#
Console.WriteLine(user?.Name);

まずはこの5つを覚えるだけでも、C#のnullによるエラーをかなり減らせます。

まとめ

C#のnullは、「値が存在しない状態」を表す特別な値です。参照型にはnullを代入できますが、intboolDateTimeなどの値型には通常そのままでは代入できません。

値型でnullを扱いたい場合は、int?DateTime?のようなnull許容型を使います。

C#
int? age = null;
DateTime? birthday = null;

C# 8.0以降では、null許容参照型を使うことで、stringstring?のようにnullを許可するかどうかをコード上で明確にできます。

C#
string name = "Taro";
string? nickname = null;

nullチェックの基本は、== null!= nullです。

C#
if (name != null)
{
Console.WriteLine(name.Length);
}

また、C#にはnullを安全に扱うための便利な演算子があります。

C#
string displayName = name ?? "ゲスト";
Console.WriteLine(user?.Name);
names ??= new List<string>();

nullを正しく扱うためには、単にチェックを増やすだけでなく、nullを許可する場所を限定し、必要のないnullを設計から減らすことが大切です。

C#のnullを理解すれば、NullReferenceExceptionを防ぎやすくなり、より安全で読みやすいコードを書けるようになります。