C# の Nullable が分かる人が TypeScript の null / undefined で混乱する5つ

C# の Nullable が分かる人が TypeScript の null / undefined で混乱する5つ

みなさんこんにちは!ヒロポンです!

C# の null は、もう手に馴染んでる。int?Nullable<int>)で「値が無いかも」を表して、?. でぬるぽを避けて、?? でデフォルト値を流す。これを業務で何年もやってきた人、多いと思います。

で、TypeScript を開く。null がある。お、C# と同じやん??……と思いきや、undefined というもう一つの「空」が出てくる。しかも設定ひとつで、型が null を許したり許さなかったりする。

C# は「空 = null」の1種類でシンプルでした。TS は「空」が2つあって、安全性も設定次第。ここを知らずに書くと、コンパイルは通ったのに実行時にぬるぽ、をやります。

この記事は、C# の null / Nullable<T> を知ってる前提で、TypeScript の null / undefined で詰まる5箇所を対応で翻訳します。C# の感覚は捨てない。ズレてる部分だけ上書きする。これが狙いです。

目次

まず対応マップ: C# の null と TypeScript の null / undefined

細かい話の前に、全体の対応をこんな感じで1枚に。

C# の null/Nullable<T>/?./?? と TypeScript の null/undefined/strictNullChecks/?./??/! を、空を表す値・空の区別を有効化する手段・Nullable・演算子の観点で対応させた比較表

この表のズレてる行を、コード付きで見ていきます。

混乱①: null と undefined は別物

C# の「空」は null の1種類。TypeScript には、それが2つあります。

let a: string | undefined;       // 未代入のまま → undefined
let b: string | null = null;     // 明示的に「空」を入れた → null

console.log(a);          // undefined
console.log(b);          // null
console.log(a === b);    // false (別物)

慣習はこう。まだ値が入ってない・存在しない = undefined意図的に「空っぽ」を入れた = null

C# の null 1本で考えてると、「同じ空でしょ?」と混同します。undefined === nullfalse。型としても値としても別物。まずここを分けて考えるのが第一歩です。

混乱②: strictNullChecks を on にしないと、型が null を許す

ここが一番大事。

TypeScript は、strictNullChecks という設定が off だと、null / undefined をどの型にも代入できてしまいます。公式 Handbook にこう書かれています。

strictNullChecks が off の場合、null や undefined になりうる値も通常どおりアクセスでき、null と undefined の値はどんな型のプロパティにも代入できる。これは null チェックのない言語(例: C#、Java)の挙動に似ている。

俺の解釈はこう。strictNullChecks off は、ぬるぽ対策が入る前の古い C# とほぼ同じ世界です。null がどの型にも入って、実行時に初めて落ちる。

// strictNullChecks を off にすると、これがコンパイルを通ってしまう
let userName: string = null;
console.log(userName.length);   // 実行時に "Cannot read properties of null" で落ちる

on にすると、こうなる。型が null / undefined を厳密に区別してくれる。C# の Nullable参照型(NRT)を on にした感覚に近いです。tsconfig.json"strict": true(or "strictNullChecks": true)を入れておく。これを切ったまま書くと、TypeScript の一番の売りである null 安全が、まるごと効きません。

混乱③: ? は undefined であって、null じゃない

C# の int?Nullable<int> で、null を表します。TypeScript の ?似て非なるもの

interface User {
  name: string;
  age?: number;   // ← age は number | undefined。null ではない
}

const u: User = { name: "tanaka" };
console.log(u.age);   // undefined (null ではない)

プロパティに付ける ?(オプショナル)は、number | undefined の意味。null は含みません。明示的に null も許したいなら、age: number | null と型で書く。

C# の int?(= null)の感覚で age?: number を「null が入る」と思っていると、ここでズレます。? は undefined、| null は null。別物です。

混乱④: ?.?? は C# とほぼ同じ。ただし ?? は null も undefined も拾う

ここは朗報。C# で使ってる ?.(null 条件)と ??(null 合体)は、TypeScript でもほぼ同じ感覚で使えます。

const len = user?.name?.length ?? 0;   // 途中が空なら 0

user が null/undefined なら、その先を評価せずに止まって(?.)、最終的に空なら 0 を返す(??)。C# の user?.Name?.Length ?? 0 と、見た目もほぼ同じですよね。

違いは1つだけ。TypeScript の ?? は、null も undefined も両方「空」として拾います。C# の ?? は null だけ。TS では2種類の空があるぶん、?? が両方を面倒見てくれる。そう覚えておくと楽です。

混乱⑤: 非nullアサーション ! は「null 無視」。乱用すると実行時に落ちる

最後は、便利だけど危ないやつ。

値の後ろに付ける !(非nullアサーション)は、「これは null/undefined じゃない」とコンパイラに断言するだけの記号です。

const el = document.getElementById("app")!;   // ! で「null じゃない」と断言
el.innerHTML = "hi";   // コンパイルは通る。でも要素が無ければ実行時に落ちる

もっと小さく再現すると、こんな感じ。

function find(): string | null {
  return null;          // 実際には null が返る
}
const s = find()!;      // ! で「null じゃない」と断言。コンパイルは通る
console.log(s.length);  // 実行時に TypeError: null... で落ちる

実行結果(! を付けても、中身が null なら実行時に落ちる):

非nullアサーション ! を付けても、値が実際に null なら実行時に TypeError で落ちる

Handbook も、「値が null/undefined にならないと確実に分かっている時だけ使え」と明記しています。! は型の上で null/undefined を消すだけ。実行時の中身は1ミリも変えない

乱用すると、コンパイルは通るのに実行時にぬるぽ。C# でいう「null 許容警告を握りつぶして本番で落ちる」のと、まったく同じ事故です。! を付ける前に、本当に null じゃないと言い切れるか、一拍考える。これだけで事故はだいぶ減ります。

動かして確かめる

混乱②の strictNullChecks は、実際に tsc で挙動を見るとこんな感じで一発で腑に落ちます。

// nullcheck.ts
let userName: string = null;
console.log(userName.length);
# strictNullChecks on: コンパイル時点で弾いてくれる
tsc --strictNullChecks nullcheck.ts
# → error TS2322: Type 'null' is not assignable to type 'string'.

# strictNullChecks off にしたい時は tsconfig.json で "strictNullChecks": false にする
# (off だと型エラーは出ず、コンパイルが通って実行時に落ちる)

実行結果(strictNullChecks on で TS2322 が出る):

strictNullChecks on で null を string に代入すると TS2322 エラーで弾かれる

--strictNullChecks を付けると、nullstring に入れたその行でコンパイルエラー。off だと素通りして実行時に落ちる。この差こそが、strictNullChecks の本体です!!

ちなみに、最近の tsc は何も設定しないと strictNullChecks が on 寄りの挙動をするものもあります。tsconfig.json"strict": true を起点に、自分のプロジェクトでどっちが効いてるかは一度 tsc --showConfig で確認しておくと安心。

俺の現場メモ

C# の「空は null の1種類」の感覚のまま TS に入ると、まず①の null / undefined の使い分けで一度は「あれ?」となります。

俺もなりました。API から返ってきた値が、未設定の項目は undefined、明示的に空の項目は null で混在してて……=== null だけでチェックしてたら、undefined をすり抜けて落ちた。null チェックしてるのに、なんで落ちんねん??ってなったやつ。== null(緩い比較)なら両方拾えるんですが、それも知らないと普通に詰まる。

でも、やられたのはこの5箇所くらい。残りは C# の ?. ?? の感覚がそのまま効くし、strictNullChecks さえ on にしておけば、危ない代入はコンパイラが止めてくれます。C# で null と付き合ってきた経験は、ちゃんと土台になる。捨てなくていい。

おすすめは、新規プロジェクトなら最初に "strict": true を入れること。これだけで、null 起因の事故の大半は、こんな感じで書いてる最中に潰せます。

まとめ

C# の Nullable<T> 経験者が TypeScript の null / undefined で詰まる5つ、整理するとこうです。

  • ① null と undefined は別物 — 未代入=undefined、明示的な空=null。=== null だけだと undefined をすり抜ける
  • ② strictNullChecks は on が前提 — off は「古い C#」相当でぬるぽ放置。"strict": true を入れる
  • ? は undefined、| null は nullage?: numbernumber | undefined
  • ?.?? は C# とほぼ同じ — ただし ?? は null も undefined も拾う
  • !(非nullアサーション)は実行時の中身を変えない — 乱用するとコンパイルを通って本番で落ちる

上書きするのは「空が2つ」「strictNullChecks を on」。この2つだけ。あとは C# で培った null の知識が、そのまま TypeScript でいい感じに効いてくれます。

よくある質問

Q1. C# の int? と TypeScript の number? は同じですか?

違います。C# の int?Nullable<int> で null を表します。TypeScript の number?(プロパティの ?)は number | undefined で、null ではありません。null も許したいなら number | null、両方なら number | null | undefined と型で書きます。

Q2. null と undefined、自分のコードではどっちを使えばいいですか?

慣習では、未設定・未初期化は undefined、意図的に「空」を入れる時は null を使い分けます。ただしチーム内で統一されていれば、片方に寄せる方針もありです。大事なのは、外部 API などで両方が混在しうる前提で、== null(緩い比較で両方拾う)や ?. でまとめて扱うことです。

Q3. strictNullChecks を後から on にしたら、エラーだらけになりました。

既存コードに null チェック漏れが大量にあった、という健全な発見です。一気に直すのが大変なら、ファイル単位で対応するか、まず !?. で型を通しつつ、危ない箇所から実際のチェックに置き換えます。新規プロジェクトなら最初から on にしておくのが一番ラクです。

Q4. !(非nullアサーション)は使ってはいけないですか?

「null/undefined にならない」と確実に言える時だけ使うのが原則です。document.getElementById の直後など、存在が保証できない場面で乱用すると、コンパイルは通っても実行時に落ちます。迷うなら ?. や明示的な null チェックの方が安全です。

関連記事

以上!

「C# の null 感覚で TS 書いて undefined にやられた」経験ある人いたら、どんどんシェア待ってるぜ!!


執筆者

バイブス父さん — 業務SE 7年(SIer 正社員2 / フリーランス5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SIer の正社員からフリーランスに転じ、複数のエージェント経由で案件を回してきた経験ベースで「業務SE視点」の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524(日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次