C# の LINQ で範囲を取る Take と Skip

TakeSkip はシーケンスの一部を切り出すメソッドだ。ページネーションや部分取得によく使われる。

Take:先頭からN件取得

先頭から指定した数の要素を取得する。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var firstThree = numbers.Take(3);

foreach (var n in firstThree)
{
    Console.Write($"{n} ");  // 1 2 3
}

元のシーケンスより多い数を指定しても、存在する分だけ返る。例外は発生しない。

int[] numbers = { 1, 2, 3 };

var result = numbers.Take(10);  // 1, 2, 3(3件しかない)

Skip:先頭からN件スキップ

先頭から指定した数の要素を飛ばし、残りを返す。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var afterThree = numbers.Skip(3);

foreach (var n in afterThree)
{
    Console.Write($"{n} ");  // 4 5 6 7 8 9 10
}

全要素数より多くスキップすると、空のシーケンスが返る。

int[] numbers = { 1, 2, 3 };

var result = numbers.Skip(10);  // 空
Console.WriteLine(result.Any());  // False

ページネーション

SkipTake を組み合わせると、ページング処理ができる。

var allProducts = GetAllProducts();  // 全商品を取得

int pageSize = 10;
int pageNumber = 3;  // 3ページ目(0から始める場合は2)

var page = allProducts
    .Skip((pageNumber - 1) * pageSize)
    .Take(pageSize);

foreach (var product in page)
{
    Console.WriteLine(product.Name);
}

3ページ目(21〜30件目)を取得している。

Skip

指定した数の要素を飛ばす

Take

指定した数の要素を取る

TakeLast / SkipLast

末尾からの操作には TakeLastSkipLast を使う。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var lastThree = numbers.TakeLast(3);   // 8, 9, 10
var exceptLast = numbers.SkipLast(3);  // 1, 2, 3, 4, 5, 6, 7

TakeLast は末尾からN件、SkipLast は末尾のN件を除いた残りを返す。

TakeWhile / SkipWhile

条件を満たす間だけ取得・スキップすることもできる。

int[] numbers = { 1, 2, 3, 4, 5, 1, 2, 3 };

// 3より小さい間だけ取得
var takeResult = numbers.TakeWhile(n => n < 3);  // 1, 2

// 3より小さい間はスキップ
var skipResult = numbers.SkipWhile(n => n < 3);  // 3, 4, 5, 1, 2, 3

TakeWhile は条件を満たさない要素が出た時点で終了する。SkipWhile は条件を満たさなくなった時点から残りをすべて返す。

注意点として、一度条件を満たさなくなったら、その後は条件を再評価しない。

int[] numbers = { 1, 2, 5, 1, 2 };

var result = numbers.SkipWhile(n => n < 3);
// 結果: 5, 1, 2
// (後半の 1, 2 も含まれる)

範囲演算子(C# 8.0 以降)

C# 8.0 以降では、配列に対して範囲演算子を使う方法もある。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Take(3) と同じ
var firstThree = numbers[..3];  // 1, 2, 3

// Skip(3) と同じ
var afterThree = numbers[3..];  // 4, 5, 6, 7, 8, 9, 10

// Skip(2).Take(4) と同じ
var middle = numbers[2..6];  // 3, 4, 5, 6

範囲演算子は配列に対してのみ使え、IEnumerable<T> には使えない。また、即座に新しい配列を作成する(即時評価)。

Chunk(.NET 6 以降)

シーケンスを指定サイズのグループに分割する。

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

var chunks = numbers.Chunk(3);

foreach (var chunk in chunks)
{
    Console.WriteLine(string.Join(", ", chunk));
}
// 1, 2, 3
// 4, 5, 6
// 7, 8, 9
// 10

バッチ処理やページング処理に便利だ。

実用例:ランキング表示

var scores = new List&lt;Player&gt;
{
    new Player { Name = "Alice", Score = 950 },
    new Player { Name = "Bob", Score = 1200 },
    new Player { Name = "Charlie", Score = 800 },
    new Player { Name = "Diana", Score = 1100 },
    new Player { Name = "Eve", Score = 1000 }
};

// 上位3名を表示
var topThree = scores
    .OrderByDescending(p => p.Score)
    .Take(3);

int rank = 1;
foreach (var player in topThree)
{
    Console.WriteLine($"{rank++}. {player.Name}: {player.Score}");
}
// 1. Bob: 1200
// 2. Diana: 1100
// 3. Eve: 1000

OrderByDescending で並べ替えてから Take で上位を切り出す。よくあるパターンだ。