C# Tips | コレクション・LINQ:Chunk分割

C# C#
スポンサーリンク

はじめに:「Chunk 分割」は“データを小分けにして扱う”ための技

業務でデータを扱っていると、こういう状況がよく出てきます。

一度に 10 万件は処理したくないので、1000 件ずつに分けて順番に処理したい。
外部 API が「最大 100 件までしか一度に送れない」ので、リクエストを 100 件単位に分割したい。
画面表示用に、一覧を「1 ページ 50 件」に区切りたい。

こういう「大きなコレクションを、一定件数ごとの“かたまり(チャンク)”に分割したい」ときに使えるのが、Chunk です。
C# 10 / .NET 6 以降では、LINQ に標準で Chunk 拡張メソッドが用意されています。

ここから、初心者向けに

Chunk の基本的な動き
実際のコード例
業務での典型的な使いどころ
注意しておきたいポイント

を、例題付きでかみ砕いて説明していきます。


Chunk の基本:「一定件数ごとの小さな配列に切り分ける」

まずは動きをコードで見る

Chunk は、シーケンスを「指定したサイズごとの配列」に分割してくれるメソッドです。

using System;
using System.Linq;

var numbers = Enumerable.Range(1, 10); // 1 ~ 10

var chunks = numbers.Chunk(3);

foreach (var chunk in chunks)
{
    Console.WriteLine($"[{string.Join(", ", chunk)}]");
}
C#

このコードの出力はこうなります。

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]

3 件ずつに分割され、最後のチャンクだけは 1 件だけになっています。
ここでの重要ポイントは、「Chunk(n) は“n 件ごとの配列の列”を返す」ということです。
戻り値の型は IEnumerable<T[]> です。
つまり、「チャンク 1 つ 1 つが T[](配列)」になっています。


Chunk の戻り値のイメージをしっかりつかむ

「二重の列」になっていることを意識する

Chunk の戻り値は IEnumerable<T[]> なので、「外側の列」と「内側の配列」があるイメージです。

外側:チャンクの列(1 チャンク、2 チャンク、3 チャンク…)
内側:そのチャンクの中身(最大 n 件の要素)

さきほどの例でいうと、

外側の列
├─ 内側の配列 [1, 2, 3]
├─ 内側の配列 [4, 5, 6]
├─ 内側の配列 [7, 8, 9]
└─ 内側の配列 [10]

という構造になっています。

だからこそ、foreach も二重になります。

foreach (var chunk in chunks)          // チャンクごと
{
    foreach (var x in chunk)           // チャンクの中の要素ごと
    {
        Console.WriteLine(x);
    }
}
C#

ここでの重要ポイントは、「Chunk を使うと“二重構造”になる」という感覚を持つことです。
外側は「かたまりの列」、内側は「かたまりの中身」です。


実務での使いどころ 1:バッチ処理を小分けにする

大量データを一定件数ずつ処理する

例えば、「10 万件のレコードを、1000 件ずつ DB に書き込む」ようなバッチ処理を考えます。

var allItems = GetManyItems(); // IEnumerable<Item> を返すとする

foreach (var chunk in allItems.Chunk(1000))
{
    SaveBatch(chunk); // 1000 件(最後だけ 1000 未満)の単位で保存
}
C#

SaveBatch の中身は、例えばこんなイメージです。

void SaveBatch(IEnumerable<Item> items)
{
    // トランザクションを張って、一括で INSERT / UPDATE するなど
}
C#

ここでの重要ポイントは、「Chunk を使うことで、“バッチサイズ”という業務的な単位をコードにそのまま表現できる」ことです。
for ループでインデックスをいじりながら分割するより、意図が圧倒的に読みやすくなります。


実務での使いどころ 2:外部 API の「最大件数制限」に合わせる

「一度に 100 件まで」のような制限にぴったり

外部 API で、こういう仕様はよくあります。

「1 リクエストで送信できるデータは最大 100 件まで」
「ID は 50 件までしか指定できない」

この場合、ID の一覧を Chunk で分割して、チャンクごとに API を呼び出すのが定番パターンです。

var ids = GetTargetIds(); // IEnumerable<string>

foreach (var idChunk in ids.Chunk(100))
{
    CallExternalApi(idChunk); // 100 件ずつ送る
}
C#

CallExternalApi の中では、チャンクをそのまま配列やリストに変換して使えます。

void CallExternalApi(IEnumerable<string> ids)
{
    var request = new Request
    {
        Ids = ids.ToArray()
    };

    // API 呼び出し
}
C#

ここでの重要ポイントは、「Chunk を使うと、“API の制限に合わせて分割している”ことがコードから一目で分かる」ことです。
マジックナンバーの 100 も、「API の仕様に合わせたバッチサイズ」として意味を持ちます。


実務での使いどころ 3:ページングや画面表示の分割

シンプルなページングの土台として使う

画面で「1 ページ 50 件」の一覧を作りたいときにも、Chunk は使えます。

var all = GetAllItems(); // IEnumerable<Item>

var pages = all.Chunk(50).ToList(); // 各要素が「1 ページ分」の配列

var page1 = pages[0]; // 1 ページ目
var page2 = pages[1]; // 2 ページ目
C#

本格的なページングは DB 側でやることが多いですが、
「とりあえずメモリ上のコレクションをページに分けたい」という用途なら、Chunk で十分です。

ここでの重要ポイントは、「Chunk は“ページサイズごとの配列”を作るので、そのままページングの単位として扱える」ということです。


Chunk を使うときの注意ポイント

チャンクサイズは 1 以上でなければならない

Chunk に 0 や負の値を渡すと、例外になります。

var numbers = Enumerable.Range(1, 10);

// これは ArgumentOutOfRangeException
var chunks = numbers.Chunk(0);
C#

チャンクサイズは必ず 1 以上にしてください。
業務ユーティリティとしてラップするなら、「0 以下なら例外を投げる」か「1 に丸める」など、ルールを決めておくとよいです。

戻り値が「配列」であることを意識する

ChunkT[] を返します。
配列なので、Count ではなく Length プロパティを使うこともできます。

foreach (var chunk in numbers.Chunk(3))
{
    Console.WriteLine($"Count = {chunk.Length}");
}
C#

もちろん、IEnumerable<T> として扱うこともできるので、chunk.Sum()chunk.Select(...) など、普通の LINQ も使えます。

ここでの重要ポイントは、「チャンクは配列なので、配列としても LINQ としても扱える」という柔軟さです。

遅延実行であることを理解する

Chunk は他の LINQ と同じく「遅延実行」です。
Chunk を呼んだだけでは実際の分割は行われず、foreach などで列挙したタイミングで初めて処理されます。

var chunks = numbers.Chunk(3); // ここではまだ何も起きていない

foreach (var chunk in chunks)  // ここで初めて分割される
{
    ...
}
C#

これは通常はメリットですが、「一度だけ使いたいのに何度も列挙してしまう」と、
そのたびに分割処理が走ることになります。
必要に応じて ToList()ToArray() で確定させるかどうかを選んでください。


まとめ:「Chunk 分割ユーティリティ」は“大きな塊を現実的なサイズに刻む道具”

Chunk の本質は、

「大きなシーケンスを、扱いやすいサイズの“かたまり”に切り分ける」

ことです。

押さえておきたいポイントは、

Chunk(n) は「n 件ごとの T[] の列」を返す。
外側が「チャンクの列」、内側が「チャンクの中身」という二重構造になる。
バッチ処理、外部 API の最大件数制限、簡易ページングなど、業務での出番が多い。
チャンクサイズは 1 以上。0 以下は例外。
遅延実行なので、列挙タイミングで分割される。

ここまで腹落ちしていれば、
「for でインデックスをいじりながら手作業で分割する」コードから卒業して、
“意図がそのまま読める Chunk ベースの分割処理”を、落ち着いて書けるようになります。

タイトルとURLをコピーしました