はじめに:「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 に丸める」など、ルールを決めておくとよいです。
戻り値が「配列」であることを意識する
Chunk は T[] を返します。
配列なので、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 ベースの分割処理”を、落ち着いて書けるようになります。
