C# LINQ → TypeScript Array methods 翻訳早見表 — Where/Select/GroupBy が filter/map/reduce にどう写るか

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

ある朝、客先で「来週からフロントの TypeScript も少し手伝ってもらえます?」って言われたこと、ないですか??

俺、最初これでめっちゃ詰まりました。C# WinForms 一筋 7 年、LINQ で Where().Select().GroupBy() を書いてきた人間が VS Code 開いて、配列を扱う場面で「ん?Where どこ??」ってマジで 30 分手が止まった。

調べたら、TypeScript には LINQ そのものは無いけど Array methods で 8 割写る、ってのが結論でした。WherefilterSelectmapAggregatereduce。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();

C# LINQ Where→Select の実行結果 (警告は無害)

TypeScript 側でこれと同じことをやりたい。products.Where(...) と書いて IntelliSense が反応しない、products.where(...) (小文字) でも出てこない。「LINQ どこいった」と独り言が出た瞬間がありました。

正解はこれです。

const names = products
    .filter(p => p.price >= 1000)
    .map(p => p.name);

Wherefilter で、Selectmap。一旦これだけ覚えると、9 割の場面で生きていけるようになります。

C# LINQ ↔ TypeScript Array methods 対応マップ (7 種)

業務 SE が日常で使う LINQ メソッドを Array methods に対応させると、こんな感じです。

C# LINQ の Where/Select/SelectMany/GroupBy/OrderBy/Aggregate/Any の 7 メソッドと TypeScript Array methods (filter/map/flatMap/reduce/sort/reduce/some) の対応

朝の現場で「C# のあれって TS で何やっけ??」と詰まった時、この表 1 枚あれば 30 秒で思い出せます。

対応 1: Wherefilter (脳死で書ける優等生ペア)

これは最初に覚える 1 ペア。同じ思想で動きます。

C# Where と TypeScript filter で価格 1000 円以上のフィルタを書き比べる

C# 側は .ToList() で実体化が必要ですが、TS 側は filter が既に新配列を返すので不要。ここは TS の方が短く書けます。

対応 2: Selectmap (これも優等生ペア)

Selectmap も「各要素に関数を適用して新配列を返す」という思想は同じです。

C# Select と TypeScript map で商品リストから名前配列を取り出す書き比べ

業務系で重宝するのは Where → Select の連結。LINQ メソッドチェーンと同じノリで .filter().map() を繋げます。

対応 3: SelectManyflatMap (1 段 flatten)

SelectMany は「各要素から配列を取り出して、それを 1 つに繋げる」動き。flatMap がほぼ同じ動きをします。

C# SelectMany と TypeScript flatMap で注文ごとの商品リストを 1 配列に flatten する書き比べ

落とし穴: flatMap1 段だけ flatten します。[[1,2],[3,4]] のような 2 重配列を完全に潰したい時は arr.flat(2) を使う必要があります。C# SelectMany も同じく 1 段なので、思想は揃ってます。

対応 4: GroupByreduce 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;
}, {});

TypeScript reduce で GroupBy 自前実装の実行結果

C# だとこの 1 ブロックが products.GroupBy(p => p.Category) の 1 行で済むので、初見はマジで「これがモダン JS??」と独り言が出ます。

ES2024 環境なら以下のように書けて、これがいちばん LINQ に近い書き心地です。

// ES2024+ / Node 21+
const grouped = Object.groupBy(products, p => p.category);

ES2024 Object.groupBy のネイティブ実行結果

業務系の現場だと TypeScript のターゲットが ES2020 や ES2017 のままで Object.groupBy が使えないことも多いので、reduce 版を 1 個コピペで持っておくと安心です。

対応 5: OrderBysort (★破壊性の罠)

OrderBysort は、戻り値の意味が違います。

// 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);

sort 破壊性 vs spread sort で元配列が変わるかの実行差分

C# 出身者がここでやらかすのが「sort を呼んだ後に元配列を使い回す」パターン。元の products の並びが変わってるので、後段の処理がバグります。

業務系で同じデータを複数箇所で参照する画面 (DataGridView の表示用と帳票出力用、みたいな) を扱ってる時、この罠で 30 分溶かしたことがあります。OrderBy のつもりで sort 呼んだら元配列が壊れる、と覚えておくと安全です。

対応 6: Aggregatereduce (型推論の罠)

Aggregatereduce は思想が同じですが、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 / Allsome / 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.jsontargetES2024 以上ならネイティブ実装が使えるので、まずそこを確認するのが最短ルート。

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());

C# ハンズオン Where→GroupBy→ToDictionary の実行結果

// 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])
);

TypeScript ハンズオン filter→Object.groupBy→fromEntries の実行結果 (C# と同一)

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.groupBy or 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) を使うか、lodashchain を使うのが現場の選択肢です。業務系の中規模配列 (数千件以下) なら、即時評価で困ることはまずありません。

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>() に近い動きです。

関連記事

以上!


執筆者

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

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

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

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

コメント

コメントする

CAPTCHA


目次