C#では、nullを扱う場面がとても多くなっています。
参照型だけでなく値型もnullを受け取れるようになり、さらにnullチェックを簡単にする演算子も増えました。
本記事では、C#のnull許容型とnull合体演算子??に焦点を当てて、基礎から実践的な使い方までを丁寧に解説します。
初心者の方でも、コード例と図解を通して直感的に理解できるように進めていきます。
C#のnull許容型とは何か
nullの基本と「値がない」という状態

C#では、「まだ値が決まっていない」「値が存在しない」という状態をnullで表現します。
参照型(クラスや配列、stringなど)は元々nullを代入できますが、intやdoubleなどの値型は、従来はnullを扱うことができませんでした。
しかし、現実のシステムでは「入力されていない数値」や「計測されていない温度」のように、数値でありながら「未定義」「未設定」という状態を表したいことがあります。
このギャップを解消するために導入されたのがnull許容型(nullable型)です。
値型にnullを許可するnullable型の仕組み

nullable型は、元の値型に「nullであるかどうか」の情報を追加した型です。
C#では、値型に?を付けることで、その値型をnull許容にできます。
// int(整数)をnull許容にした例
int? age = null; // 年齢が未入力の状態を表現できる
// 値を代入することも可能
age = 25;
// boolやdoubleなど、他の値型でも同様に?を付けるだけ
bool? isMember = null;
double? temperature = 36.5;
nullable型は内部的には「値があるかどうかのフラグ」と「値本体」をセットで扱っているとイメージすると理解しやすくなります。
nullable型とnull合体演算子??の基本的な違い
役割の違いを整理する
nullable型とnull合体演算子??は、よく一緒に使われますが、役割はまったく異なります。
| 要素 | 役割 | 使い方の例 |
|---|---|---|
| nullable型 | 値型がnullを取れるようにする型の仕組み | int?、DateTime? など |
null合体演算子?? | nullだった場合の「代わりの値」を簡単に指定する演算子 | x ?? 0 など |
nullable型は「型」の話であり、??は「式(演算)」の話です。
つまり、nullable型で「nullを許可する」→??演算子で「nullのときの代替を決める」という流れで使うことが多いです。
null合体演算子??の基本動作

null合体演算子??は、左側の値がnullなら右側の値を返すという非常にシンプルな演算子です。
string? name = null;
// name が null の場合は "名無し" を使う
string displayName = name ?? "名無し";
Console.WriteLine(displayName);
名無し
このように、「nullチェック + 代替値の指定」を1行で書けるため、コードの可読性が大きく向上します。
nullable型(int?など)を実際に使ってみる
int?などの値型での使用例

nullable型は、値型であっても「値がある状態」と「値がない(null)状態」を行ったり来たりできます。
using System;
class Program
{
static void Main()
{
// テストの点数。まだ受験していない場合はnull
int? score = null;
// HasValue プロパティで、値が入っているかを確認できます
if (score.HasValue)
{
Console.WriteLine("点数: " + score.Value);
}
else
{
Console.WriteLine("まだテストを受けていません");
}
// テストを受けたので点数を代入
score = 85;
if (score.HasValue)
{
Console.WriteLine("点数: " + score.Value);
}
}
}
まだテストを受けていません
点数: 85
HasValueとValueをセットで使うことで、nullable型から安全に値を取り出せます。
nullを許容することで表現力が増す場面
nullable型を使うことで、次のような「現実の状態」を自然にコードで表現できます。
- アンケートの任意項目(回答していない場合はnull)
- データベースの列がNULLを許可している数値カラム
- センサーからの計測値が取得できなかった場合
「0という値」と「まだ値が決まっていない状態」を区別できる点が、nullable型最大のメリットです。
null合体演算子??の実践的な使い方
基本パターン: 「nullならデフォルト値」

null合体演算子??のもっとも基本的な使い方は、「nullの場合のデフォルト値」を決めることです。
using System;
class Program
{
static void Main()
{
string? inputName = null; // ユーザーが入力しなかったケースを想定
string displayName = inputName ?? "ゲスト"; // nullなら「ゲスト」
Console.WriteLine("ようこそ、" + displayName + "さん");
}
}
ようこそ、ゲストさん
nullチェックのif文を書かなくても、1行で代替値のロジックを記述できます。
multiple ?? で優先度をつける
??は連結して使うこともできます。
左から順に「nullでない最初の値」が採用されます。
string? nameFromForm = null; // フォーム入力
string? nameFromProfile = "太郎"; // プロフィール情報
string defaultName = "ゲスト";
string displayName = nameFromForm ?? nameFromProfile ?? defaultName;
Console.WriteLine(displayName);
太郎
この例では、フォーム入力がnullなのでスキップされ、プロフィールの値「太郎」が採用されています。
nullable型と??を組み合わせた具体例
int?と??を使って0をデフォルトにする

nullableな数値を合計したい場合、nullをそのまま計算すると扱いに困ります。
その際に??で0に置き換えてから計算すると、安全かつシンプルです。
using System;
class Program
{
static void Main()
{
int? stockA = 10;
int? stockB = null; // 情報未取得
int? stockC = 5;
// nullなら0として扱って合計
int total = (stockA ?? 0) + (stockB ?? 0) + (stockC ?? 0);
Console.WriteLine("合計在庫数: " + total);
}
}
合計在庫数: 15
このようにnull合体演算子を使うことで、計算前のnullチェックを毎回書く必要がなくなります。
DateTime?と??で「日付未設定」の扱いを決める
using System;
class Program
{
static void Main()
{
DateTime? lastLogin = null; // まだログインしたことがないユーザー
// nullなら「システム開始日」を代わりに使う
DateTime displayDate = lastLogin ?? new DateTime(2000, 1, 1);
Console.WriteLine("最終ログイン日: " + displayDate.ToString("yyyy/MM/dd"));
}
}
最終ログイン日: 2000/01/01
「ビジネス上の意味を持つデフォルト値」をセットしておくと、画面表示やログ出力がわかりやすくなります。
C# 8以降の「nullable 参照型」との違いに注意
nullable参照型は「型アノテーション + 警告」の仕組み

C# 8以降では、参照型にも?を付けられるnullable参照型が導入されました。
int?やdouble?などはnullable値型string?やMyClass?などはnullable参照型
どちらも?を使うため混同しがちですが、nullable参照型は「コンパイラがnull安全をチェックし、警告を出してくれる仕組み」であり、実行時の挙動が大きく変わるわけではありません。
一方、nullable値型(int?など)は、実際に「値があるかどうか」を内部に保持する構造です。
??は参照型でも値型でも使える
null合体演算子??は、参照型・値型を問わず使える演算子です。
string? title = null;
string safeTitle = title ?? "タイトル未設定";
int? count = null;
int safeCount = count ?? 0;
「nullかもしれないもの」から「必ず値があるもの」へ変換する場面で、型の種別に関係なく活躍します。
null合体代入演算子??=との違いと使い分け
??= は「その変数自体を書き換える」

null合体演算子??に似た演算子として、null合体代入演算子??=があります。
??=は、左辺がnullのときだけ右辺の値を代入する演算子です。
using System;
class Program
{
static void Main()
{
int? value = null;
// value が null のときだけ 10 を代入する
value ??= 10;
Console.WriteLine(value); // 10 と表示
// 2回目はすでに10が入っているので、代入されない
value ??= 20;
Console.WriteLine(value); // 10 のまま
}
}
10
10
?? は「結果を得る」演算子、??= は「変数そのものを書き換える」演算子と覚えておくと整理しやすくなります。
null許容型と??を安全に使うためのポイント
nullを「意味のある状態」として扱う
nullable型や??は便利ですが、「なんとなく全部null許容にする」のは危険です。
nullであること自体に意味があるかどうかを考えて設計することが重要です。
- 本当に「未入力」「未決定」を表現したい → null許容にする
- 常に値があるべき(0や空文字で表現できる) → できれば非nullにしておく
??で「隠れたバグ」を作らない工夫
??は手軽な反面、「本来nullであってはいけないのに、デフォルト値でごまかしてしまう」危険もあります。
たとえば、ユーザーIDがnullだったときに?? 0としてしまうと、「ID0のユーザー」と混同するかもしれません。
ビジネス上ありえない値をデフォルトにする、あるいはthrowで例外を投げる、などの設計も検討すべきです。
int? userId = null;
// 0を使うと「本物のユーザーID 0」と紛らわしい場合は避ける
int id = userId ?? throw new InvalidOperationException("ユーザーIDが設定されていません");
まとめ
C#のnull許容型(nullable型)は、値型にもnullという「値がない状態」を持たせる仕組みであり、現実世界の「未入力」「未決定」を自然に表現できます。
一方で、null合体演算子??は、nullであった場合に使う代替値を1行で指定できる演算子です。
これらを組み合わせることで、int?やDateTime?などを安全かつ読みやすく扱えるようになります。
ただし、何でもかんでもnull許容にしたり、安易に??でデフォルト値を埋めると、問題のあるデータを見逃してしまうこともあります。
「nullであることに意味があるか」を意識しながら、nullable型と??を適切に使い分けることが、堅牢で読みやすいC#コードへの近道です。
