動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

C# の enum 感覚で詰まる TypeScript の enum(数値 / 文字列 / const / union の違い)

バイブス父さん
現役の業務SE
2026年9月20日11 min read広告 (PR) を含む場合があります
C# の enum 感覚で詰まる TypeScript の enum(数値 / 文字列 / const / union の違い)

C# の enum 感覚で詰まる TypeScript の enum(数値 / 文字列 / const / union の違い)

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

「来月からフロントも TypeScript でお願い」。

業務系一筋でやってきた身に、これがいきなり降ってくると地味に身構えますよね。

私も最初、C# の enum のノリで TypeScript の enum を書きました。で、生成された JavaScript を見て「あれ??なんかオブジェクトができてる」と固まった。

この記事は、TypeScript enum C# の感覚のズレで詰まる人向けです。C# の enum を基準に、TS の数値 enum・文字列 enum・const enum・union 型リテラルがそれぞれ何に対応するのか、対応マップと実コードで整理していきます。

先に言っておくと、C# の知識はムダになりません。「C# のあれが TS だとこれ」という対応さえ掴めば、TS の enum 周りはだいたい読めるようになります。

C# の enum のつもりで書くと「あれ?」となる場面

C# の enum は、コンパイル時に確定する整数の名前付き定数。実行時に何かオブジェクトが生えたりはしません。

ところが TS の数値 enum を書いて、出力された JS を見ると。

オブジェクトが1個できています。しかも値から名前を逆引きできる。

C# 脳だと「なんでランタイムに実体があるんだ??」となる。ここが最初の段差です。

私はこれを知らずに、enum を大量に定義したモジュールのバンドルサイズを見て「意外と重いな」と首をひねったことがあります。原因は、enum が実行時オブジェクトとして全部バンドルに乗っていたから。

まずは全体像を対応マップで掴みます。

C# enum ↔ TypeScript の対応マップ

C# の enum が、TS の4つの表現にどう対応するか。並べてみます。

C# enum と TS の数値enum・文字列enum・union型を、実行時オブジェクト生成・逆引き・型安全・Flags・バンドルサイズの観点で比較した対応マップ

ざっくり言うと、C# の enum の代わりに TS でまず考えるのは union 型です。

実行時オブジェクトを作らないぶん軽くて、型安全も C# の enum と同じくらい効きます。

逆引きや文字列表現が要る時だけ enum を使う。このくらいの温度感が、現場では扱いやすい。

順番に対応を見ていきます。

対応1: C# の数値 enum ↔ TS の数値 enum

C# の素直な enum は、TS でもそのまま数値 enum で書けます。見た目はほぼ同じ。

// C# の enum Status { Todo, Doing, Done } に相当
enum Status {
  Todo,   // 0
  Doing,  // 1
  Done,   // 2
}

console.log(Status.Todo);   // 0
console.log(Status[0]);     // "Todo" ← 値から名前を逆引きできる

実行結果:

Status.Todo が 0、Status[0] が Todo と逆引きされる実行結果

Status.Todo0 なのは C# と同じ。

でも Status[0]"Todo" が返るのは、C# に無い挙動です。TS の数値 enum は、実行時にこんな感じの双方向オブジェクトを生成しているから。

// 上の enum が生成する JS のイメージ
var Status = {
  "0": "Todo", "1": "Doing", "2": "Done",
  Todo: 0, Doing: 1, Done: 2,
};

逆引きが要る場面ではこれが効きます。数値で DB に保存した値を、画面に名前で出す、みたいなやつ。

逆に、要らないなら。この実体はただの重りになります。

対応2: C# の文字列運用 ↔ TS の文字列 enum / union 型

C# で「区分を文字列で持ちたい」時、enum を ToString() したり、定数文字列を並べたりしますよね。

TS だと選択肢が2つあります。

// (A) 文字列 enum:実行時オブジェクトは作るが、逆引きは無い
enum Color {
  Red = "RED",
  Green = "GREEN",
}
console.log(Color.Red);   // "RED"

// (B) union 型リテラル:実行時オブジェクトを作らない
type Size = "S" | "M" | "L";
const picked: Size = "M";   // "S" | "M" | "L" 以外はコンパイルエラー

実行結果(文字列 enum の値と、union 型の代入):

文字列 enum Color.Red が RED と出力される実行結果

(B) の union 型が、TS だと一番出番が多い書き方です。

type Size = "S" | "M" | "L" と書くだけ。C# の enum と同じく「決まった値しか入らない」型安全が、いい感じに得られます。しかも実行時に何も生成しないので軽い。

「文字列 enum と union、どっちにするか??」で迷ったら、実行時にオブジェクトが要るかどうかで決めると楽です。要らなければ union。

対応3: C# の [Flags] ↔ TS は自前でビット演算

ここが一番ハマるところ。C# の [Flags] 属性に相当する仕組みは、TS の enum にはありません。

// [Flags] は無いので、数値 enum + ビット演算で自前で組む
enum Permission {
  None  = 0,
  Read  = 1 << 0,  // 1
  Write = 1 << 1,  // 2
  Admin = 1 << 2,  // 4
}

const p = Permission.Read | Permission.Write;          // 3
const canWrite = (p & Permission.Write) === Permission.Write;  // true
console.log(p, canWrite);   // 3 true

実行結果:

Permission のビット演算で p=3、canWrite=true になる実行結果

ビット演算(| & <<)そのものは、C# と同じ書き方で動きます。

違うのは、C# の [Flags] が持っている「ToString() でフラグ名を Read, Write と自動連結する」挙動が TS には無いこと。名前で出したいなら、そこは自分でループを書いて組み立てます。

[Flags] のつもりで enum に属性を付けようとして「あれ、そんな構文無い」となるやつ。先に知っておくと詰まりません。

ミニマム検証: 数値 enum を union に書き換えてみる

「まず union に寄せる」の感触を掴むハンズオン。数値 enum を union に置き換えると、こう変わります。

数値 enum を union 型リテラルに書き換える before/after。enum 定義が type の union に変わり、関数の引数型はそのまま使える

after 側は、実行時オブジェクトを一切作りません。

label の引数は、相変わらず「決まった値しか受け付けない」型のまま。C# の enum で守っていた型安全は、union でもそのまま維持できます!!

ハマりポイント: 知らないと地味に効くやつ

C# 脳のまま書くと踏むやつを2つ。

① 数値 enum は実行時オブジェクトぶん重い

対応1で見たとおり、数値 enum も文字列 enum も、実行時に双方向のオブジェクトを生成します。enum を大量に定義すると、そのぶんバンドルに乗る。

「型を縛りたいだけ」なのに enum を並べていると、フロントのバンドルが地味に膨らみます。

逆引きが要らないなら union に寄せる。これだけで無駄が減ります。

② const enum は「軽いけど配布で困る」ことがある

「実行時オブジェクトが嫌なら const enum があるじゃん」と思うかもしれません。

const enum はコンパイル時に値へインライン展開されるので、実行時オブジェクトを作りません。軽い。

ただし、isolatedModules(Babel や一部のバンドラが要求する設定)の下では、const enum に制約が出ます。ライブラリとして配布するコードだと、const enum を避けて union で書く、という判断になることが多い。

「軽いから」で安易に全部 const enum。これは後で刺さることがあります。

私の現場メモ: C# 出身者が enum で迷わないコツ

キャッチアップしていて一番効いたのは、「TS の enum は C# の enum と別物」と割り切ったことでした。

名前が同じだから同じものだと思って読むと、実行時オブジェクトの有無でいちいち引っかかる。

「TS の enum は、実行時に実体を持つ特殊な子」「型を縛りたいだけなら union」。この2軸で見るようになってから、迷わなくなりました。

もう1つ。C# の [Flags] みたいな「言語が面倒を見てくれる機能」は、TS だと自前で書くことが多いです。

これは TS が劣ってるという話じゃなくて、設計思想の違い。C# の便利機能に慣れた身だと最初は面倒に感じますが、「ここは手書きするゾーン」と割り切ると、逆に何が起きてるかいい感じに見通せます。

C# でビット演算や型の縛りを普通に書けていた人なら、TS の enum 周りは十分に読めます。既知の知識が、そのまま橋渡しになる領域です。

まとめ

C# 出身者向けに、TS の enum を整理し直すと要点はこうです。

  • TS の数値 enum は実行時オブジェクトを生成し、値から名前を逆引きできる(C# に無い挙動)
  • 型を縛りたいだけなら union 型リテラルtype X = "A" | "B")が第一候補。軽くて型安全
  • 文字列 enum は実行時オブジェクトを作るが逆引きは無い。文字列で残したい時に
  • C# の [Flags] 相当は無い。数値 enum + ビット演算で自前、ToString() の連結も手書き
  • const enum は軽いが isolatedModules で制約あり。配布コードでは避けられることも

「TS の enum は C# の enum と別物」と割り切って、まず union を第一候補にする。これだけで、enum 周りの迷いはだいぶ消えます。

よくある質問

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

似ていますが挙動が違います。TS の数値 enum は実行時に JavaScript のオブジェクトを生成し、値から名前を逆引きできます。C# の enum はコンパイル時の定数で、実行時オブジェクトは作りません。TS では多くの場合、union 型リテラルの方が軽くて型安全です。

Q2. enum と union 型はどちらを使うべきですか?

実行時オブジェクトが不要で、値の集合を型で縛りたいだけなら union 型リテラル(type X = "A" | "B")が第一候補です。バンドルサイズが最小で、C# の enum 相当の型安全も得られます。値から名前の逆引きが必要な場合だけ、数値 enum を検討します。

Q3. C# の [Flags] enum は TypeScript にありますか?

[Flags] 属性に相当する仕組みはありません。数値 enum とビット演算(1 << 0 など)で自前で組みます。C# のように ToString() が自動でフラグ名を連結する挙動は無いので、名前で出したい部分は手書きになります。

Q4. const enum は普通の enum と何が違いますか?

const enum はコンパイル時に値へインライン展開され、実行時オブジェクトを生成しません。バンドルが軽くなりますが、isolatedModules 環境(Babel や一部のバンドラ)では制約があるため、ライブラリ配布では避けられることもあります。

次に読むべき記事

以上!

同じところで詰まった人いたら、どんどんシェア待ってるぜ!!


執筆者

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

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


この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について