みなさんこんにちは!ヒロポンです!
DB の数値カラムが NULL のとき、C# 側でどう受ければいいんだ…ってなったこと、ないですか??
int で受けると詰む。未入力の NULL も「0」も、同じ 0 に潰れて区別がつかない。「在庫数 0」なのか「まだ登録されてない」のか、コードからは見分けられなくなるんですよね。地味な話に見えて、業務システムだとこれがけっこう刺さる。
ここで効くのが C# Nullable int?。値型に null を持たせる Nullable<T> です。
この記事は「なんで int? が要るのか」から入ります。そこから HasValue / GetValueOrDefault / null 合体演算子 ?? / null 条件演算子 ?. を、DB の未入力値を安全に受け取るコピペパターンとして積み上げていく流れ。
田村さんの現場に多い .NET Framework 4.7.2 でも普通に動く構文に絞ってます。
なぜ int? が要るのか — NULL と 0 が区別できない問題
まず、なんで int? なんてものが要るのか。
int は値型です。必ず何かしらの値を持つし、初期値は 0。null にはなれない。
ところが DB の世界には NULL(未入力・不明)がある。「数量未定」と「数量0」は意味が全然違うのに、int で受けると両方 0 になって混ざります。
int qty = 0; // これは「0個」? それとも「まだ入力されてない」?
この「値が無い状態」を型として表現できるのが Nullable<T>、省略形の int? です。
int? は「int の値、または null」のどちらか。これで NULL と 0 をちゃんと区別できます。
Nullable 値型の基本 — HasValue / Value / GetValueOrDefault
基本はこの3つ。int? に値があるか調べて、取り出すだけ。
int? age = null; // 値型なのに null を持てる
age = 30;
// 値があるか確認
if (age.HasValue)
Console.WriteLine(age.Value); // 30
// null なら既定値、値があればその値
int x = age.GetValueOrDefault(); // null → 0、値あり → その値
int y = age.GetValueOrDefault(-1); // null → -1 を既定にできる
HasValue が true か false、Value が中身。GetValueOrDefault() は「null なら既定値(int なら 0)、あれば中身」を1メソッドで返してくれます。
GetValueOrDefault(-1) みたいに既定値を渡せるのもいい感じで、「未入力は -1 扱い」みたいな現場ルールにそのまま乗る。
ここまでが土台。この上に演算子を2つ重ねると、もっと短く書けます。
null 合体演算子 ?? — null の時の既定値を1行で
?? は「左が null なら右を使う」演算子。GetValueOrDefault を演算子1個で書けると思えばいい。
int? input = null;
int count = input ?? 0; // input が null なので 0
int? stock = 5;
int shown = stock ?? 0; // stock は 5 なので 5
input ?? 0 で「input が null なら 0、そうでなければ input の中身」。DB から受けた int? を画面表示用に既定値で埋めるとき、これが定番です。こんな感じで1行で済む。
null 条件演算子 ?. — null なら触らずに抜ける
?. は「左が null なら、その先を呼ばずに null を返す」演算子。null チェックを書かずにメンバアクセスできます。
int[] nums = null;
// ❌ これは NullReferenceException
// int len = nums.Length;
// ✅ nums が null なら len も null (例外にならない)
int? len = nums?.Length;
// ?. と ?? の合わせ技: null なら 0
int safeLen = nums?.Length ?? 0;
nums?.Length は、nums が null だと null を返す(だから受け側は int?)。そこに ?? 0 を足せば「null なら 0」までセットで書けて、いい感じに短くなる。
この ?. と ?? の合わせ技、業務コードでめちゃくちゃ出てきます。覚えておくと手数が減ります!!
DB から int? に落とす — DBNull との橋渡し
実務で一番使うのがこれ。DataReader や DataTable から、NULL 許容の数値を int? で受け取るパターンです。
// DataReader から: DBNull なら null、そうでなければ int
int? qty = reader["Qty"] == DBNull.Value
? (int?)null
: reader.GetInt32(reader.GetOrdinal("Qty"));
// DataTable の DataRow からなら Field<int?> が楽
int? price = row.Field<int?>("Price"); // DBNull は自動で null になる
DataRow.Field<int?>("列名") は、DB の NULL を勝手に int? の null に変換してくれる。DBNull.Value との比較を自分で書かずに済みます。
💡 DBNull の安全なハンドリング全般は SQL Server の DBNull を C# で安全に扱う5つのイディオム でまとめてます。今回は「受けた後の int? の扱い」に寄せた内容です。
これで「受け取る」側は固まりました。あとは踏みやすい罠を2つ潰しておきます。
ハマりポイント: 知らないと一晩飛ぶやつ
int? まわりで業務SEがよく踏むのは、この2つです。
① null のまま .Value を開いて InvalidOperationException
正直に言うと、俺もこれで30分溶かしました。
int? が null の状態で .Value を読むと、例外で落ちる。しかも NullReferenceException じゃなくて InvalidOperationException。エラーメッセージでピンとこないと、余計にハマるやつです。
int? n = null;
int bad = n.Value;
// ❌ InvalidOperationException: Nullable object must have a value.
実行結果(NullReferenceException ではなく InvalidOperationException が出る・Mono で再現):

対処はシンプル。.Value を読む前に HasValue で確認するか、そもそも GetValueOrDefault() か ?? で受ける。.Value を直に開くのは「絶対値がある」と確信できる時だけにしておくと安全です。
② ?? の優先順位が低くて、意図と違う値が入る
これも一回やられました。?? は演算子の優先順位がかなり低い。+ や * より後に評価されます。
int? x = 5;
int wrong = x ?? 0 + 10; // 結果は 5
int right = (x ?? 0) + 10; // 結果は 15
実行結果(?? は + より優先順位が低いので意図とズレる・.NET 9 で実機確認):

x ?? 0 + 10 は、+ が先に効くので x ?? (0 + 10) と解釈されます。x は 5(null じゃない)なので、そのまま 5。「0 で埋めてから +10 したかった」つもりが、まるごと違う値になる。ん?なんで +10 が消えた??ってなるやつです。
しかもエラーは出ない。値だけ静かにズレる。一番こわいパターンです。
教訓は、?? を計算式と混ぜるときは、迷わずカッコで囲む。(x ?? 0) + 10 と書けば意図どおりです。
俺の現場ではこう使ってる
流通系の基幹システムの保守をやってた頃、DB の数量カラムが NULL 許容で、画面表示と集計でしょっちゅう NULL と 0 の扱いを取り違えてました。ほんま地味に効いてくるんですよね、これ。
そこからの落とし込みはシンプルで、こう決めてます。
- DB から受ける数値は、NULL 許容なら必ず
int?で受ける(intで潰さない) - 画面表示の直前で
?? 0かGetValueOrDefault()で確定させる(表示は 0、判定は null のまま) .Valueは HasValue 確認後だけ。それ以外は??かGetValueOrDefaultに寄せる
書き方の使い分けは1枚にまとめておきます。「これ HasValue で分岐すべき? ?? でいい?」と迷ったら、ここを見てください。

まとめ
要点はこれだけ。
int?(Nullable<T>)は「値、または null」。DB の NULL と 0 をちゃんと区別できる- 基本は
HasValue/Value/GetValueOrDefault()。null なら既定値が欲しいなら?? - メンバアクセスを安全にしたいなら
?.、?? 0と合わせて「null なら既定値」 .Valueを null で開くとInvalidOperationException、??を計算式と混ぜるならカッコで囲む
C# の null まわりは細かい挙動が多い。言語仕様系の解説書を1冊手元に置いておくと、この手の「なんで?」を調べる時間がまるっと浮きます。
このパターン、DB から数値を受ける処理にそのまま貼って使ってください。
よくある質問
Q1. int? と Nullable は違いますか?
同じものです。int? は Nullable<int> の省略記法で、コンパイル結果も同一。どの値型にも付けられるので、double? や DateTime?、bool? も同じように使えます。読みやすいぶん、通常は int? の形を使います。
Q2. GetValueOrDefault() と ?? はどう使い分けますか?
結果はほぼ同じです。age.GetValueOrDefault(0) と age ?? 0 は同じ意味になる。?? は演算子で簡潔なので式の中で使いやすく、GetValueOrDefault はメソッドなので「既定値を明示してる感」が出ます。チーム内で読みやすい方に寄せて構いません。
Q3. .NET Framework 4.7.2 でも ?. や ?? は使えますか?
使えます。null 条件演算子 ?. は C# 6、null 合体演算子 ?? はそれ以前から使えるので、.NET Framework 4.7.2(C# 7.3)で問題なく動きます。ただし C# 8 の ??=(null 合体代入)はこの環境では使えないので、x = x ?? 0; のように書いてください。
Q4. 三項演算子(? :)と ?? はどちらを使うべきですか?
「null なら既定値」だけなら ?? のほうが短く読みやすいです。x != null ? x : 0 は x ?? 0 で済む。条件が null 判定以外(範囲チェックなど)を含むなら三項演算子、純粋に null 埋めなら ??、と分けると意図が伝わりやすくなります。
次に読むべき記事





以上!
同じ NULL と 0 の取り違えで詰まった人いたら、どんどんシェア待ってるぜ!!
執筆者
バイブス父さん — 業務 SE 7 年 (SIer 正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SIer の正社員からフリーランスに転じ、 複数のエージェント経由で案件を回してきた経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る




