記事内に広告が含まれています

C#入門⑩|nullとnull許容型の基本

C#入門⑩ 基本文法・構文解説

前回はrecord型の基本とクラスとの使い分けについて解説しました。

今回は、C#におけるnullとnull許容型(nullable)の基本について解説します。

nullはC#の実務でも頻繁に登場する概念であり、正しく扱わないとバグや例外の原因になります。

初心者がつまずきやすいポイントを押さえながら、nullを安全に扱うための方法を学びましょう。

nullとは?

C#におけるnull(ヌル)とは、「何のオブジェクトも参照していない状態」を示す特別な値です。

変数にnullが代入されている場合は、その変数は「どこも参照していない」「何のデータも入っていない」という意味になります。

nullは「データが存在しない」「値が未設定」という状態を表す便利な手段ですが、うっかり扱うとNullReferenceExceptionという実行時エラーを引き起こす可能性があります。

これは「nullが入った変数に対してプロパティやメソッドを呼び出そうとした」ときに発生します。

nullは便利な一方で、正しく扱わないとバグの原因になります。(実際の開発業務において、nullが原因のバグに遭遇する頻度はかなり高いです)

C#では「この変数はnullになるかもしれない」と意識してコードを書くことがとても大切です。

値型と参照型におけるnullの扱いの違い

C#の型は「値型」と「参照型」に大きく分かれ、それぞれでnullの扱いが異なります。

値型は通常nullを持てない

C#のintやboolといった値型は、メモリ上に実体の値を直接持つため、通常はnullを代入することはできません。

int num = null; // コンパイルエラー

nullを代入しようとすると以下のようにエラーになります。

しかし、実務では「値が未設定」を表したい場面もあります。

そのような場合には、null許容値型(Nullable<T>型)を使うことで、intやboolなどの値型でもnullを扱えるようになります。

C#では型名の後ろに?(クエスチョンマーク)を付けることで、nullを許容する値型(int?など)を定義できます。

// null許容値型であればnullを代入できる
int? score = null;

if (score == null)
{
    Console.WriteLine("スコア未設定です");
}

score = 100;
Console.WriteLine(score.Value); // 100

参照型はnullを許容する

C#のstringやclassなどの参照型は、オブジェクトそのものではなく、そのオブジェクトの場所(参照)を持ちます。

そのため、参照型の変数には何も割り当てていない状態(=まだどこも参照していない状態)としてnullを代入することができます

string s = null;

C# 8.0より前のバージョンでは、参照型はすべてnullを許容するという仕様だったため、コード上ではどの変数もnullになり得ました。

その結果、「この変数はnullかもしれない」という前提でコードを書く必要があり、nullチェックを忘れると実行時にエラーが発生するというリスクが常にありました。

null許容型とC# 8.0以降の変化

こうした問題を解決するために、C# 8.0からnull許容参照型(Nullable参照型)という仕組みが導入されました。

この機能を使うと、参照型に対しても「nullを許容するかどうか」を明示できるようになります。

null許容参照型の例

C#8.0以降であれば、値型と同様に、型名の後ろに「?」を付けることで「この変数はnullを許容する」と明示できます。

逆に、?を付けていない変数にnullを代入しようとすると、コンパイラが警告を出してくれるようになります。

string title = null;        // null非許容の参照型(デフォルト)。nullを代入すると警告が出る
string? description = null; // null許容の参照型。nullを代入しても警告は出ない

Console.WriteLine(title);
Console.WriteLine(description);

null非許容の参照型に、nullを代入しようとすると以下のような警告が表示されます。

重要なのは、エラーではなく警告であるという点です。警告が出てもコード自体はコンパイルできますが、「この使い方はバグの原因になるかも」と注意を促してくれます。

null許容参照型とnull安全

null許容参照型が導入されたことで、「nullになる可能性がある変数」と「nullにならない前提の変数」をコンパイル時に区別できるようになりました。

これにより、「nullが原因でアプリが落ちる」といった事故が起きるリスクを大幅に減らすことができます。

このような仕組みを「null安全(null safety)」と呼び、null許容参照型の導入によってC#もnull安全を意識した開発が可能な言語へと進化しました。

補足:null許容参照型の有効化

null許容参照型の機能は、.NET 6以降であればC#プロジェクト作成時にデフォルトで有効になっています

プロジェクトファイル(.csproj)に以下のような設定が自動で入るためです。

<Nullable>enable</Nullable>

一方、古いプロジェクトではこの設定がないこともあるため、その場合は手動で追加するか、必要なファイルにだけ #nullable enable を記述する必要があります。

// ファイルの先頭に記載するとnull許容参照型がファイル単位で有効になる
#nullable enable

string title = null;
string? description = null;

基本的にnull許容参照型の機能は有効化すべきですが、既存のプロジェクトで一度に有効化すると警告が大量に出ることがあるので、実務で有効化を行う際にはチームや組織の方針に従って段階的に進めることをおすすめします。

nullを扱うための便利な演算子と構文

C#では、nullの可能性がある変数に安全にアクセスしたり、デフォルト値を補ったりするために、さまざまな便利な構文が用意されています。

ここからは実務でよく使う以下の構文について、それぞれの役割と使い方を解説します。

  • null条件演算子(?.
  • null合体演算子(??
  • null合体代入演算子(??=
  • nullチェック構文(is null / is not null
  • null免除演算子(!

null条件演算子(?.)

null条件演算子は、「変数がnullかもしれないが、プロパティやメソッドにアクセスしたい」というときに使います。

nullでないときだけアクセスが行われ、nullだった場合はnullが返ります。

string? name = null;
int? length = name?.Length; // nameがnullでもエラーにならない(nullが返る)

このように、?. を使えば null による NullReferenceException を避けることができます。

null合体演算子(??)

null合体演算子は、左側の値がnullだった場合に、右側の値を代わりに使う演算子です。

string? input = null;
string value = input ?? "未入力"; // inputがnullの場合は"未入力"が代入される

ユーザー入力や外部データの初期値設定などでよく使われます。

null合体代入演算子(??=)

null合体代入演算子(C# 8.0以降)は、変数がnullだった場合にだけ代入を行います。

string? message = null;
message ??= "デフォルトメッセージ"; // messageがnullの場合は"デフォルトメッセージ"が代入される
Console.WriteLine(message); // "デフォルトメッセージ"

設定済みの値を上書きしたくないときに便利です。

nullチェック構文(is null / is not null)

C# 7.0以降で is nullが、C# 9.0以降で is not null という構文が使えるようになりました。

従来の == null よりも直感的に読めるため、読みやすさを重視する現場でよく使われます。

string? name = null;

if (name is null)
{
    Console.WriteLine("名前が未設定です");
}

if (name is not null)
{
    Console.WriteLine($"名前:{name}");
}

null免除演算子(!)

null免除演算子(!(変数の後ろにビックリマーク))は、「この変数はnullではない」とコンパイラに伝えるための演算子です。

「nullにならないことが確実」で、C#コンパイラのnull警告を抑制したい場面で使用します。

string? name = GetName();
Console.WriteLine(name!.Length); // nameはnullでないと明示する。もしnullなら実行時にエラーになる

ただし、もし実行時にnullだった場合は NullReferenceException になるため、注意して使うようにしましょう。

実務でのnull対策とベストプラクティス

C#でnullを安全に扱うための演算子や構文を紹介しましたが、それらのみに頼るのではなく、設計や実装の段階で「nullが入りにくいコードにする」ことが重要です。

ここでは、C#での開発現場において「nullによるバグや例外を未然に防ぐ」ための設計方針やコーディングの工夫を、初心者でもすぐに実践できる形でまとめています。

nullを避ける設計

クラスのプロパティやフィールドには、できる限り初期値を設定するのが基本です。

nullになり得る状態を避けることで、その後のnullチェックが不要になります。

public class Order
{
    public List<Item> Items { get; set; } = new(); // 初期値に空のリストを指定する
}

nullを返さないメソッド設計

メソッドからの戻り値でnullを返すのは、後続の処理でnullチェックが必要になる原因になります。

空のリストや空文字を返す方が安全です。

public List<string> GetTags() => _tags ?? new List<string>();

文字列は””で未入力を表現する

文字列の場合は、nullよりも空文字("")を使う方が扱いやすいケースが多くあります。

string.IsNullOrEmpty() を使えば、nullと空文字の両方を一度にチェックできます。

string? userInput = null;

// nullよりも空文字を使うことで扱いやすくなる
var safeValue = userInput ?? "";

if (string.IsNullOrEmpty(safeValue))
{
    Console.WriteLine("入力がありません");
}

LINQでのnull対策

LINQはコレクションの処理を簡潔に書けますが、対象のリストや要素にnullが含まれているとエラーが発生することがあります。

var names = people.Where(p => p.Name.StartsWith("A")); // peopleがnullならエラー

このような場合、事前にnull対策を入れて安全に扱いましょう。

var names = (people ?? new List<Person>())
                .Where(p => p != null && p.Name != null)
                .Select(p => p.Name);

あるいは、nullの要素や型が合わない要素を除外するOfType<T>()を使う方法もあります。

var names = people
                .OfType<Person>() // nullやPerson型でない要素を除外
                .Select(p => p.Name);

初心者のうちは、「listがnullかもしれない」「中身のpもnullかもしれない」と常に意識し、どこでnullが入り得るかを考える習慣をつけましょう。

まとめ

nullはC#の実務で避けて通れない概念です。正しく扱うためには、以下のポイントを意識しましょう。

  • どの変数・プロパティがnullになり得るかを常に意識する
  • null許容型と非null許容型を明示的に使い分ける
  • nullチェックや??, ?.演算子などを活用して安全にコードを書く
  • 設計段階でnullを減らす工夫をする

これらを意識することで、nullによるバグや例外を大幅に減らすことができます。

次回は例外処理の基本について解説します。引き続き実務に役立つC#の知識を身につけていきましょう。

コメント

タイトルとURLをコピーしました