LINQの「遅延実行」と「即時実行」
C#のLINQ(Select, Whereなど)を使用する際、非常に重要な概念があります。それは、これらのメソッドが返す値は「データそのもの(結果の集合)」ではなく、「クエリ(命令の手順)」であるということです。
// この時点では計算は行われず、クエリが定義されただけ(遅延実行)
var query = sourceData.Where(x => x > 10);
このクエリは、foreachループで回すか、今回紹介するToArrayやToListメソッドを呼び出すまで実行されません。これを「遅延実行(Deferred Execution)」と呼びます。
クエリの結果を確定させ、配列やリストといった具体的なコレクションとしてメモリ上に保持したい場合、ToArrayまたはToListメソッドを使用して「即時実行(Immediate Execution)」を行います。
この記事では、これらのメソッドの使い方と、それぞれの適切な使い分けについて解説します。
1. ToArray メソッド(配列への変換)
ToArrayメソッドは、シーケンス(IEnumerable<T>)のすべての要素を評価し、新しい**配列(Array)**を作成して返します。
- 用途: 要素数が固定で、これ以上追加・削除を行わない場合。
- 特徴: メモリ効率が若干良いですが、サイズの変更はできません。
コード例:文字列配列を数値配列に変換
数値が含まれた文字列の配列を、int型の配列に変換する例です。
using System;
using System.Collections.Generic;
using System.Linq;
public class ToArrayExample
{
public static void Main()
{
// 元データ(文字列の配列)
string[] rawScores = { "85", "92", "78", "100", "64" };
Console.WriteLine("--- ToArray の実行 ---");
// Select で文字列を int に変換し、ToArray で配列として確定させる
// この時点でクエリが実行され、メモリ上に int[] が生成される
int[] scoresArray = rawScores
.Select(s => int.Parse(s))
.ToArray();
// 配列なので、インデックスを使ってアクセス可能
Console.WriteLine($"要素数: {scoresArray.Length}");
Console.WriteLine($"先頭の要素: {scoresArray[0]}");
// 配列の内容を表示
Console.WriteLine(string.Join(", ", scoresArray));
}
}
出力結果:
--- ToArray の実行 ---
要素数: 5
先頭の要素: 85
85, 92, 78, 100, 64
2. ToList メソッド(Listへの変換)
ToListメソッドは、シーケンスのすべての要素を評価し、新しい**List<T>(ジェネリックリスト)**を作成して返します。
- 用途: 変換後に要素を追加・削除したい場合や、
List<T>の機能(Sort,Findなど)を使いたい場合。 - 特徴: 配列よりも柔軟に扱えます。
コード例:条件に合うデータをリスト化
大量のデータから条件に合致するものだけを抽出し、後から編集可能なリストとして保持する例です。
using System;
using System.Collections.Generic;
using System.Linq;
// 商品クラス
public class ProductItem
{
public string Name { get; set; }
public int Price { get; set; }
}
public class ToListExample
{
public static void Main()
{
var products = new List<ProductItem>
{
new ProductItem { Name = "Keyboard", Price = 5000 },
new ProductItem { Name = "Mouse", Price = 3000 },
new ProductItem { Name = "Monitor", Price = 20000 },
new ProductItem { Name = "USB Cable", Price = 1000 }
};
Console.WriteLine("--- ToList の実行 ---");
// 価格が 4000円 以上の商品を抽出し、List<ProductItem> に変換
List<ProductItem> expensiveItems = products
.Where(p => p.Price >= 4000)
.ToList();
// List<T> なので、後から要素を追加できる
expensiveItems.Add(new ProductItem { Name = "Headset", Price = 8000 });
Console.WriteLine($"リストの要素数: {expensiveItems.Count}");
foreach (var item in expensiveItems)
{
Console.WriteLine($"{item.Name}: {item.Price:C0}");
}
}
}
出力結果:
--- ToList の実行 ---
リストの要素数: 3
Keyboard: ¥5,000
Monitor: ¥20,000
Headset: ¥8,000
実体化の重要性:「クエリのキャッシュ」
ToArrayやToListを使用する大きなメリットの一つは、クエリの結果をキャッシュ(保存)できる点です。
IEnumerable<T>のままで保持していると、その変数をforeachなどで使用するたびに、毎回クエリ(計算処理)が再実行されてしまいます。重い処理を含む場合や、データソース(DBやファイル)の内容が変わる可能性がある場合は、一度ToListなどで実体化しておくことが推奨されます。
// 遅延実行(まだ計算されていない)
var query = GetDataFromHeavyProcess();
// ここで計算実行 1回目
foreach (var item in query) { ... }
// ここで計算実行 2回目 (無駄な処理が発生)
foreach (var item in query) { ... }
// --- 改善策 ---
// ここで計算実行し、結果をメモリに保存
var resultList = GetDataFromHeavyProcess().ToList();
// メモリ上のリストを参照するだけ(高速)
foreach (var item in resultList) { ... }
まとめ
ToArray(): クエリ結果を固定長の配列として取得します。参照用データとして最適です。ToList(): クエリ結果を可変長のリストとして取得します。データの加工が必要な場合に最適です。- 即時実行: これらのメソッドを呼ぶと、その時点でクエリが実行され、結果が確定(実体化)します。
LINQを使用する際は、どこまでをクエリ(遅延実行)として扱い、どのタイミングで実体(即時実行)に変換するかを意識することが、パフォーマンスの良いコードを書く鍵となります。
