みなさんこんにちは!ヒロポンです!!
ある朝、客先で「来週からフロントの TypeScript も少し手伝ってもらえます?」って言われたこと、ないですか??
俺、最初これでめっちゃ詰まりました。C# WinForms 一筋 7 年、LINQ で Where().Select().GroupBy() を書いてきた人間が VS Code 開いて、配列を扱う場面で「ん?Where どこ??」ってマジで 30 分手が止まった。
調べたら、TypeScript には LINQ そのものは無いけど Array methods で 8 割写る、ってのが結論でした。Where は filter、Select は map、Aggregate は reduce。1 対 1 で対応する。
ただし 3 つだけ罠があって、それを踏むと「TypeScript なんもわからん」ってなります。今回は C# LINQ → TypeScript Array methods の対応 7 種 と、業務 SE がハマる落とし穴 3 つを、コードペアで開示します。
この記事は連載「TypeScript for C# 出身者」の 1 本目です。
俺が TypeScript に出会った朝 — LINQ が見当たらない焦り
最初に詰まったのは、商品リストから「価格 1000 円以上のものを抽出して、名前だけ取り出す」という処理。C# なら脳死で書ける。
var names = products
.Where(p => p.Price >= 1000)
.Select(p => p.Name)
.ToList();

TypeScript 側でこれと同じことをやりたい。products.Where(...) と書いて IntelliSense が反応しない、products.where(...) (小文字) でも出てこない。「LINQ どこいった」と独り言が出た瞬間がありました。
正解はこれです。
const names = products
.filter(p => p.price >= 1000)
.map(p => p.name);
Where が filter で、Select が map。一旦これだけ覚えると、9 割の場面で生きていけるようになります。
C# LINQ ↔ TypeScript Array methods 対応マップ (7 種)
業務 SE が日常で使う LINQ メソッドを Array methods に対応させると、こんな感じです。

朝の現場で「C# のあれって TS で何やっけ??」と詰まった時、この表 1 枚あれば 30 秒で思い出せます。
対応 1: Where ↔ filter (脳死で書ける優等生ペア)
これは最初に覚える 1 ペア。同じ思想で動きます。

C# 側は .ToList() で実体化が必要ですが、TS 側は filter が既に新配列を返すので不要。ここは TS の方が短く書けます。
対応 2: Select ↔ map (これも優等生ペア)
Select も map も「各要素に関数を適用して新配列を返す」という思想は同じです。

業務系で重宝するのは Where → Select の連結。LINQ メソッドチェーンと同じノリで .filter().map() を繋げます。
対応 3: SelectMany ↔ flatMap (1 段 flatten)
SelectMany は「各要素から配列を取り出して、それを 1 つに繋げる」動き。flatMap がほぼ同じ動きをします。

落とし穴: flatMap は 1 段だけ flatten します。[[1,2],[3,4]] のような 2 重配列を完全に潰したい時は arr.flat(2) を使う必要があります。C# SelectMany も同じく 1 段なので、思想は揃ってます。
対応 4: GroupBy ↔ reduce or Object.groupBy (★最大の罠)
ここが TypeScript 移行で一番ハマるところ。C# LINQ の GroupBy に直接相当する標準メソッドが、長らく無かったんです。
ES2024 で Object.groupBy がやっと入りましたが、古い TypeScript / Node / ブラウザ環境では使えません。なので業務 SE が現場で目にするのはだいたい reduce での自前実装です。
// TypeScript: GroupBy 相当を reduce で自前実装
type Product = { category: string; name: string; price: number };
const grouped = products.reduce<Record<string, Product[]>>((acc, p) => {
(acc[p.category] ??= []).push(p);
return acc;
}, {});

C# だとこの 1 ブロックが products.GroupBy(p => p.Category) の 1 行で済むので、初見はマジで「これがモダン JS??」と独り言が出ます。
ES2024 環境なら以下のように書けて、これがいちばん LINQ に近い書き心地です。
// ES2024+ / Node 21+
const grouped = Object.groupBy(products, p => p.category);

業務系の現場だと TypeScript のターゲットが ES2020 や ES2017 のままで Object.groupBy が使えないことも多いので、reduce 版を 1 個コピペで持っておくと安心です。
対応 5: OrderBy ↔ sort (★破壊性の罠)
OrderBy と sort は、戻り値の意味が違います。
// C#: 新しいシーケンスを返す・元の products は不変
var sorted = products.OrderBy(p => p.Price).ToList();
// TypeScript: sort は破壊的・元の products も並び変わる
products.sort((a, b) => a.price - b.price);
// 元配列を変えたくない場合は spread で新配列を作る
const sorted = [...products].sort((a, b) => a.price - b.price);

C# 出身者がここでやらかすのが「sort を呼んだ後に元配列を使い回す」パターン。元の products の並びが変わってるので、後段の処理がバグります。
業務系で同じデータを複数箇所で参照する画面 (DataGridView の表示用と帳票出力用、みたいな) を扱ってる時、この罠で 30 分溶かしたことがあります。OrderBy のつもりで sort 呼んだら元配列が壊れる、と覚えておくと安全です。
対応 6: Aggregate ↔ reduce (型推論の罠)
Aggregate と reduce は思想が同じですが、TypeScript 側は seed (初期値) が必須 で、累積型の推論が C# より厳しい。
// C#: Aggregate は seed なし版もある
var total = products.Aggregate(0m, (acc, p) => acc + p.Price);
// TypeScript: reduce は seed なしだと累積型推論で詰まる
const total = products.reduce((acc, p) => acc + p.price, 0);
// 累積が配列やオブジェクトの場合は明示型が必要
const grouped = products.reduce<Record<string, number>>((acc, p) => {
acc[p.category] = (acc[p.category] ?? 0) + p.price;
return acc;
}, {});
C# だと推論が効いて省ける seed が、TypeScript だと明示しないとビルドエラーになる場面が多いです。エラーが「Type 'string' is not assignable to type 'never'」みたいな初見殺しなので、慣れるまでは reduce に必ず seed と型引数を渡すクセを付けると詰まりにくい。
対応 7: Any / All ↔ some / every
ここも優等生ペアです。
// C#
bool hasExpensive = products.Any(p => p.Price >= 10000);
bool allInStock = products.All(p => p.Stock > 0);
// TypeScript
const hasExpensive = products.some(p => p.price >= 10000);
const allInStock = products.every(p => p.stock > 0);
名前は違いますが動きは完全に同じ。Any = some (1 つでもあるか) / All = every (全部か) で記憶しとけば迷いません。
ハマりポイント 3 つ — 知らないと一晩飛ぶやつ
⏱ 対処目安: 全部合わせて 30 分以内に潰せます。
① GroupBy 相当が標準で無い (⏱ 10 分)
ES2024 で Object.groupBy が入る前は、reduce で自前実装するのが定石。C# 出身者が「GroupBy どこ??」と探して見つからず、ググって辿り着くのが reduce 版です。プロジェクトの tsconfig.json の target が ES2024 以上ならネイティブ実装が使えるので、まずそこを確認するのが最短ルート。
② sort は破壊的 (⏱ 10 分)
C# OrderBy のつもりで sort を呼ぶと、元配列の並びが変わって後段でバグる。回避は [...arr].sort(...) で新配列を作る、もしくは arr.toSorted(...) (ES2023+) を使う。これは tsconfig の target で使えるかどうか変わるので、現場のチーム規約を見て選ぶといいです。
③ reduce の型推論が厳しい (⏱ 10 分)
累積が単純な数値ならいいですが、配列やオブジェクトを累積する場合は型引数 (reduce<RecordType>(...)) を明示しないと推論が壊れます。C# Aggregate だと素直に通る場面で、TypeScript の reduce だと「Type 'X' is not assignable to type 'never'」系のエラーが出る。慣れの問題ですが、初見だと混乱します。
ミニマム検証のハンズオン
商品 5 件の配列で、「価格 1000 円以上のものをカテゴリ別にグルーピングして、件数を出す」という処理を C# / TypeScript の両方で書き比べてみます。
// C# 側 (3 行で済む)
var counts = products
.Where(p => p.Price >= 1000)
.GroupBy(p => p.Category)
.ToDictionary(g => g.Key, g => g.Count());

// TypeScript 側 (ES2024 / Object.groupBy 使用)
const filtered = products.filter(p => p.price >= 1000);
const grouped = Object.groupBy(filtered, p => p.category);
const counts = Object.fromEntries(
Object.entries(grouped).map(([k, v]) => [k, v?.length ?? 0])
);

C# が 3 行、TypeScript が 4-5 行。微妙に長くなるのが TypeScript 側の構造的な特徴ですが、思考のステップ数は同じです。
俺の現場メモ — TypeScript キャッチアップで効いたコツ
最初の 1 週間は「C# のあれって TS で何書くんやっけ??」のリストを Notion に貯めました。Where → filter / Select → map / GroupBy → reduce or Object.groupBy / OrderBy → 破壊的 sort、みたいな対応表を自分で作る。これだけで脳の置き換え速度が 3 倍になります。
もう 1 つ効いたのは、C# の感覚で書いてから TypeScript に翻訳するステップを意識的に踏むこと。いきなり TypeScript で書こうとすると、Array methods の名前思い出しでフリーズします。先に頭の中で C# LINQ を組み立てて、それを 1 メソッドずつ翻訳する方が安定します。
LINQ で培った「データ → 抽出 → 変換 → 集約」のメソッドチェーン思考は、Array methods で 8 割そのまま使える。むしろ C# 出身者は、JavaScript 専業の人より Array methods をスラスラ書ける場面が多いです。
まとめ
C# LINQ → TypeScript Array methods は 7 メソッドで 8 割の場面をカバー できます。
- 優等生ペア: Where ↔ filter / Select ↔ map / Any ↔ some / All ↔ every
- 思想は同じだが書き方が違う: SelectMany ↔ flatMap / Aggregate ↔ reduce
- 罠付き: GroupBy → ES2024
Object.groupByor reduce 自前 / OrderBy → sort は破壊的
業務 SE が客先で TypeScript の手伝いを頼まれた時、この 7 種の対応表 1 枚あれば 9 割の場面で生き残れます。残り 1 割は「Object.groupBy が tsconfig target で使えるか」「sort の破壊性」「reduce の型推論」の 3 つだけ。
C# LINQ で培った思考は、TypeScript Array methods にほぼそのまま写ります。C# 出身者が TypeScript で出遅れる理由はない、というのが俺の現場感覚です!!
連載 2 本目では「C# 静的型 ↔ TypeScript 型推論の違いと罠 10 選」を予定してます。客先で TS 案件のオファーが来た時の弾薬として、合わせて押さえておいてください。
よくある質問
Q1. LINQ の Select の中で複数フィールド返したい時、TypeScript ではどう書くんですか?
A. オブジェクトリテラルで返します。C# の匿名型 new { p.Name, p.Price } に相当するのが TypeScript の { name: p.name, price: p.price }。型はインライン推論されます。
Q2. C# の .ThenBy() (2 番目のソート キー) は TypeScript で何ですか?
A. sort のコンパレータ関数内で 2 段階比較を書きます。(a, b) => a.x - b.x || a.y - b.y のように || で繋ぐのが定石。C# の OrderBy().ThenBy() ほど読みやすくはないですが、動作は同じです。
Q3. IEnumerable<T> 的な遅延評価は TypeScript にもありますか?
A. Array methods は基本的に 即時評価 です。C# LINQ の遅延評価相当をやりたい場合は、Generator 関数 (function* / yield) を使うか、lodash の chain を使うのが現場の選択肢です。業務系の中規模配列 (数千件以下) なら、即時評価で困ることはまずありません。
Q4. 大量データの場合、Array methods は遅くないですか?
A. filter().map().reduce() のチェーンは各段で配列を作るので、100 万件級だとオーバーヘッドが見えます。その場合は for ループ or for...of で 1 パスにまとめると劇的に速くなる。ただし業務系の通常画面 (数百-数千件) なら気にしなくていい範囲です。
Q5. C# Cast<T>() 相当は TypeScript にありますか?
A. TypeScript は静的型なので、ランタイム キャストは基本不要です。型を絞り込みたい時は arr.filter((x): x is Product => x.kind === 'product') のように type predicate (x is Product) を使うと、後段の型推論が効きます。これは C# OfType<T>() に近い動きです。
関連記事
- EF6 + LINQ で N+1 問題を踏まない3つの書き方 — C# 側で LINQ を引き続き使う時の最重要パターン
- C# Linq で Null を回避する書き方とパフォーマンス(業務SEのコピペで動くやつ) — LINQ の Null セーフティを TypeScript の
??と比較する時の前提知識 - 【C#】Asp.net Core EF で SqlServer に対しマイグレーションをする方法 — C# 側の EF Core で書いた処理を TypeScript フロントから呼ぶ時の API 境界の話
- C# using の3形態 — using ステートメント / using 宣言 / await using で業務SE が踏む使い分け — C# 側の最新構文を押さえてからの TypeScript 移行の方が混乱が少ない
- 面談で『俺が入れば〇〇』と提示する技術アピール術 — フリーランス業務SE の単価交渉の核 — TypeScript も書ける C# 出身者が単価交渉で出す弾薬の組み立て方
以上!
執筆者
バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。現職は SEO 直轄部の AI アドバイザー兼 PL、副業で中小 SIer の CTO。SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


コメント