C#でプログラムを開発する際、避けては通れないのが「null(ヌル)」の取り扱いです。
変数がnullであるかどうかを判定し、もしnullであれば初期値を代入するという処理は、あらゆるシーンで登場します。
C# 8.0で導入された「null合体代入演算子(??=)」は、このような冗長になりがちなnullチェックと代入を、劇的に短く、そして読みやすく書き換えることができる強力な演算子です。
本記事では、この演算子の基本的な使い方から、従来の記述方法との違い、そして実践的な活用シーンまでを徹底的に解説します。
null合体代入演算子 ??= とは
??=演算子は、左辺の変数がnullである場合にのみ、右辺の値をその変数に代入する演算子です。
もし左辺がnullでない場合は、代入は行われず、元の値がそのまま保持されます。

基本的な構文
まずは、最もシンプルな構文を確認しましょう。
// 左辺の変数が null なら右辺の値を代入する
左辺 ??= 右辺;
この一行は、従来のC#で書かれていた「値がnullなら代入する」という数行のコードを凝縮したものです。
書き方がシンプルになるだけでなく、「意図が明確になる」という大きなメリットがあります。
サンプルプログラムで動作を確認する
実際にコンソールアプリケーションでどのように動作するのか、コードを見てみましょう。
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
string? message = null;
// message が null なので、"Hello, World!" が代入される
message ??= "Hello, World!";
Console.WriteLine($"1回目: {message}");
// message は既に null ではないため、この代入は無視される
message ??= "新しいメッセージ";
Console.WriteLine($"2回目: {message}");
}
}
1回目: Hello, World!
2回目: Hello, World!
この例では、最初にmessageがnullであるため、??=によって文字列が代入されています。
しかし、2回目の??=では既に値が入っているため、右辺の「新しいメッセージ」は代入されず、元の値が維持されていることがわかります。
従来の記述方法との比較
??=演算子を使うことで、コードがどれほどスッキリするのか、従来の記述方法と比較してみましょう。
以前のC#では、主にif文や三項演算子を使用していました。

if文によるチェック
最も一般的だったのは、以下のようなif文による判定です。
if (items == null)
{
items = new List<string>();
}
このコードは、やりたいことに対して記述量が多く、ネストが深くなる原因にもなります。
null合体演算子 (??) による代入
C# 2.0から存在する??(null合体演算子)を使用して代入を行うことも可能でした。
items = items ?? new List<string>();
これはif文よりは短いですが、itemsという変数名を二回書かなければならず、変数の名前が長い場合に冗長に感じられます。
null合体代入演算子 (??=) の場合
C# 8.0以降は、以下のように書くだけで済みます。
items ??= new List<string>();
このように、「変数がnullならこの値をセットする」という定型処理を最短で表現できるのが、この演算子の最大の特徴です。
?? 演算子と ??= 演算子の違い
似たような演算子に??がありますが、これらは役割が明確に異なります。
初心者が混同しやすいポイントですので、整理しておきましょう。
| 演算子 | 名称 | 主な役割 |
|---|---|---|
?? | null合体演算子 | 変数がnullの場合に別の値を返す(代入はしない) |
??= | null合体代入演算子 | 変数がnullの場合にその変数へ代入する |
?? 演算子の使いどころ
??は、元の変数の値を書き換えずに、一時的にデフォルト値を使用したい場合に適しています。
string? name = null;
// name 自体は null のままだが、表示用として "ゲスト" を使う
Console.WriteLine(name ?? "ゲスト");
??= 演算子の使いどころ
一方、??=は、その後の処理でも継続してその値を使いたい場合、つまり「変数の初期化」を行いたい場合に使用します。
実践的な活用シーン
ここからは、実際の開発で??=がどのように役立つか、具体的なケースを紹介します。
1. コレクションの遅延初期化
リストや辞書などのコレクションを使用する際、インスタンスが必要になったタイミングで生成する「遅延初期化」というテクニックがあります。

public class DataManager
{
private List<string>? _dataList;
public void AddData(string data)
{
// _dataList が null の場合のみ、新しいインスタンスを生成して代入
_dataList ??= new List<string>();
_dataList.Add(data);
}
}
この書き方により、メソッドが呼ばれるたびにnullチェックのif文を書く手間が省けます。
2. プロパティのゲッターでの利用
プロパティが初めて参照されたときに、重い処理(データベースへのアクセスや複雑な計算)の結果をキャッシュしておきたい場合にも便利です。
private string? _cachedToken;
public string AuthToken
{
// 初回アクセス時のみ FetchTokenFromApi() が実行され、代入される
get => _cachedToken ??= FetchTokenFromApi();
}
private string FetchTokenFromApi()
{
Console.WriteLine("APIからトークンを取得中...");
return "SECRET_TOKEN_123";
}
この例では、AuthTokenが2回目以降に呼ばれた際、_cachedTokenはnullではないため、API呼び出しはスキップされ、保存済みの値が返されます。
注意点と制限事項
非常に便利な演算子ですが、使用する際に気を付けるべきルールがいくつかあります。
左辺は代入可能な変数であること
??=の左辺には、変数、プロパティ、またはインデクサーの要素を指定する必要があります。
定数や読み取り専用(readonly)のフィールドに適用しようとすると、コンパイルエラーになります。
null許容型であること
当然ながら、左辺の型がnullを許容しない型(例えば、intやboolなどの非null許容値型)である場合、この演算子を使う意味がなく、多くの場合コンパイラから警告やエラーが出されます。
値を保持する変数は、string?やint?などの「null許容型」として宣言しておきましょう。
型の互換性
右辺の型は、左辺の型に代入可能な型である必要があります。
これは通常の代入と同じルールです。
object? obj = null;
string message = "Hello";
// string は object に代入可能なので OK
obj ??= message;
演算子の優先順位について
??=は代入演算子の一種です。
そのため、演算の優先順位は非常に低く設定されています。
複雑な式の中で使用する場合は、計算の順番に注意が必要です。

基本的には、式全体が評価された後に最後に代入が行われると考えて間違いありませんが、コードの可読性を保つためにも、??=を使う式はあまり複雑にしすぎないのがベストプラクティスです。
まとめ
C#の??=(null合体代入演算子)は、「変数がnullの時だけ初期化したい」という、プログラミングで頻出するパターンを簡潔に記述するためのツールです。
この演算子を活用することで、冗長なif文を減らし、コードの「ノイズ」を取り除くことができます。
特に、プロパティの初期化や、コレクションの遅延生成といった場面でその真価を発揮します。
現代のC#開発においては、null安全性を高めるコーディングが求められています。
今回ご紹介した??=を正しく使いこなし、よりスマートでメンテナンス性の高いソースコードを目指しましょう。
まずは、手元のコードにある「nullチェックと代入」をこの演算子で書き換えられないか探してみることから始めてみてください。
