C#: LINQ クエリ
1. LINQの概念と背景
LINQ(Language Integrated Query)は、C# に組み込まれたデータクエリ言語であり、統一された構文を使用してさまざまなデータソース(コレクション、データベース、XML など)に対してクエリを実行することができます。LINQ が登場する前は、異なるデータソースに対してクエリを実行するには、それぞれ異なる API を習得する必要がありましたが、LINQ はこの問題を解決しました。
▶ サンプル
using System;
using System.Linq;
int[] numbers = { 5, 12, 3, 21, 8, 15 };
var result = from n in numbers
where n > 10
select n;
foreach (var n in result)
{
Console.WriteLine(n);
}
12
21
15
2. クエリ構文とメソッド構文
LINQ には、「クエリ構文」と「メソッド構文」の 2 つのスタイルがあります。両者は機能的に同等です。メソッド構文は、より多くの演算子をサポートし、簡潔な連鎖呼び出しが可能であるため、実際にはより一般的に使用されています。
▶ サンプル
using System;
using System.Linq;
int[] numbers = { 5, 12, 3, 21, 8, 15 };
var querySyntax = from n in numbers
where n > 10
orderby n
select n;
var methodSyntax = numbers.Where(n => n > 10).OrderBy(n => n);
Console.WriteLine("Query syntax: " + string.Join(", ", querySyntax));
Console.WriteLine("Method syntax: " + string.Join(", ", methodSyntax));
Query syntax: 12, 15, 21
Method syntax: 12, 15, 21
3. from / where / select / order by / group
クエリ構文の中核となる句:fromはデータソースと範囲変数を指定し、whereはフィルタリングを行い、selectは結果を投影し、orderbyはソートを行い、group ... byはグループ化を行います。
▶ サンプル
using System;
using System.Linq;
string[] names = { "Alice", "Bob", "Anna", "Charlie", "Amy" };
var result = from name in names
where name.StartsWith("A")
orderby name.Length
select name;
foreach (var name in result)
{
Console.WriteLine(name);
}
Amy
Anna
Alice
(1) GROUP BY の例
using System;
using System.Linq;
var students = new[]
{
new { Name = "Zhang", Grade = "A" },
new { Name = "Li", Grade = "B" },
new { Name = "Wang", Grade = "A" },
new { Name = "Zhao", Grade = "C" },
new { Name = "Sun", Grade = "B" }
};
var groups = from s in students
group s by s.Grade into g
select new { Grade = g.Key, Count = g.Count() };
foreach (var g in groups)
{
Console.WriteLine($"Grade {g.Grade}: {g.Count} student(s)");
}
Grade A: 2 student(s)
Grade B: 2 student(s)
Grade C: 1 student(s)
4. 一般的な演算子
メソッド構文で一般的に使用されるLINQ演算子:
| 演算子 | 用途 |
|---|---|
| 場所 | フィルターエレメント |
| 選択 | プロジェクト/変換 |
| OrderBy / OrderByDescending | 昇順/降順で並べ替え |
| 投稿者 | 二次ソート |
| GroupBy | グループ要素 |
| First / FirstOrDefault | 最初の要素を取得 |
| Single / SingleOrDefault | 唯一の要素を取得する |
| カウント | カウント対象 |
| 任意 | 要素が存在するかどうかを確認する |
| すべて | すべての要素が条件を満たしているか確認する |
| 合計/平均 | 合計/平均 |
| 最小 / 最大 | 最小値 / 最大値 |
| スキップ/実行 | 指定した回数スキップ/実行 |
| 異なる | 重複を削除 |
| ToList / ToArray / ToDictionary | コレクションへの変換 |
▶ サンプル
using System;
using System.Linq;
int[] numbers = { 3, 7, 2, 9, 5, 7, 3, 8 };
Console.WriteLine("Where > 5: " + string.Join(", ", numbers.Where(n => n > 5)));
Console.WriteLine("Select x2: " + string.Join(", ", numbers.Select(n => n * 2)));
Console.WriteLine("OrderBy: " + string.Join(", ", numbers.OrderBy(n => n)));
Console.WriteLine("OrderByDescending: " + string.Join(", ", numbers.OrderByDescending(n => n)));
Console.WriteLine("Distinct: " + string.Join(", ", numbers.Distinct()));
Console.WriteLine("First: " + numbers.First());
Console.WriteLine("FirstOrDefault > 10: " + numbers.FirstOrDefault(n => n > 10));
Console.WriteLine("Count > 5: " + numbers.Count(n => n > 5));
Console.WriteLine("Any > 8: " + numbers.Any(n => n > 8));
Console.WriteLine("All > 1: " + numbers.All(n => n > 1));
Console.WriteLine("Sum: " + numbers.Sum());
Console.WriteLine("Average: " + numbers.Average());
Console.WriteLine("Min: " + numbers.Min());
Console.WriteLine("Max: " + numbers.Max());
Console.WriteLine("Skip 3 Take 2: " + string.Join(", ", numbers.Skip(3).Take(2)));
Where > 5: 7, 9, 7, 8
Select x2: 6, 14, 4, 18, 10, 14, 6, 16
OrderBy: 2, 3, 3, 5, 7, 7, 8, 9
OrderByDescending: 9, 8, 7, 7, 5, 3, 3, 2
Distinct: 3, 7, 2, 9, 5, 8
First: 3
FirstOrDefault > 10: 0
Count > 5: 4
Any > 8: True
All > 1: True
Sum: 44
Average: 5.5
Min: 2
Max: 9
Skip 3 Take 2: 9, 5
5. リストおよび配列でのLINQ
LINQ は、IEnumerable<T> インターフェースを実装するあらゆるオブジェクトで動作します。これには、List<T> や配列 T[] も含まれます。
▶ サンプル
using System;
using System.Collections.Generic;
using System.Linq;
List```<string>``` fruits = new List```<string>``` { "apple", "banana", "cherry", "date" };
var longNames = fruits.Where(f => f.Length > 5).Select(f => f.ToUpper());
Console.WriteLine("List query: " + string.Join(", ", longNames));
int[] scores = { 88, 72, 95, 60, 85 };
var topScores = scores.Where(s => s >= 85).OrderByDescending(s => s);
Console.WriteLine("Array query: " + string.Join(", ", topScores));
List query: BANANA, CHERRY
Array query: 95, 88, 85
6. 辞書を使ったLINQ
辞書でLINQを使用する場合、各要素はKeyValuePair<TKey, TValue>であり、.Keyおよび.Valueを介してアクセスされます。
▶ サンプル
using System;
using System.Collections.Generic;
using System.Linq;
var prices = new Dictionary`<string, decimal>`
{
{ "Apple", 5.5m },
{ "Banana", 3.2m },
{ "Orange", 7.8m },
{ "Grape", 12.0m }
};
var expensive = prices.Where(kv => kv.Value > 5m)
.OrderByDescending(kv => kv.Value)
.Select(kv => $"{kv.Key}: {kv.Value}");
foreach (var item in expensive)
{
Console.WriteLine(item);
}
var priceDict = prices.ToDictionary(kv => kv.Key, kv => kv.Value * 2);
Console.WriteLine("Doubled orange price: " + priceDict["Orange"]);
Grape: 12.0
Orange: 7.8
Apple: 5.5
Doubled orange price: 15.6
7. 遅延実行と即時実行
LINQ クエリはデフォルトで遅延実行されます。つまり、クエリを定義しても実行は行われず、結果を反復処理する(例:foreachを使用する場合など)ときに初めてクエリが実行されます。 ToList()、ToArray()、Count()、またはFirst()のようなメソッドを呼び出すと、即座に実行が開始されます。
▶ サンプル
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List```<int>``` { 1, 2, 3 };
var query = numbers.Where(n => n > 1);
numbers.Add(4);
numbers.Add(5);
Console.WriteLine("Deferred execution includes elements added later:");
foreach (var n in query)
{
Console.WriteLine(n);
}
var immediate = numbers.Where(n => n > 1).ToList();
numbers.Add(6);
Console.WriteLine("Immediate execution does not include 6 added later:");
foreach (var n in immediate)
{
Console.WriteLine(n);
}
Deferred execution includes elements added later:
2
3
4
5
Immediate execution does not include 6 added later:
2
3
4
5
💡 遅延実行とは、クエリの結果を反復処理するたびに再計算が行われることを意味します。結果を複数回使用する必要がある場合は、ToList()を呼び出して結果をキャッシュしてください。
8. IQueryable と IEnumerable の違い
IEnumerable<T>はメモリ内コレクションのクエリ(LINQ to Objects)に使用され、IQueryable<T>はリモートデータソース(データベースなど)のクエリに使用されます。これらは式ツリーをSQLなどのクエリ言語に変換し、サーバー側で実行することができます。
▶ サンプル
using System;
using System.Collections.Generic;
using System.Linq;
List```<int>``` data = new List```<int>``` { 10, 20, 30, 40, 50 };
IEnumerable```<int>``` enumerableQuery = data.Where(n => n > 25);
IQueryable```<int>``` queryableQuery = data.AsQueryable().Where(n => n > 25);
Console.WriteLine("IEnumerable result: " + string.Join(", ", enumerableQuery));
Console.WriteLine("IQueryable result: " + string.Join(", ", queryableQuery));
Console.WriteLine("IQueryable expression: " + queryableQuery.Expression);
IEnumerable result: 30, 40, 50
IQueryable result: 30, 40, 50
IQueryable expression: System.Collections.Generic.List`1[System.Int32].Where(n => (n > 25))
⚠️ EF Core のような ORM を使用する場合、IQueryableはデータベース側でフィルタリングを実行するのに対し、IEnumerableはまずすべてのデータをメモリに読み込んでからフィルタリングを行います。このため、パフォーマンスの差は大きくなる可能性があります。
❓ よくある質問
📖 まとめ
- LINQ は C# に組み込まれた統一データクエリ言語であり、コレクション、データベース、XML、その他のデータソースに対応しています
- クエリの構文はSQLに似ており、メソッドの構文はラムダ式の連鎖呼び出しを採用しています。両者を混在させて使用することも可能です。
- 主要な句:from、where、select、order by、group by
- 一般的な演算子:Where、Select、OrderBy、GroupBy、First、Count、Any、Sum、Skip、Take など。
- LINQ は、List、Array、Dictionary、および IEnumerable
<T>を実装するあらゆるオブジェクトで動作します - 実行の遅延とは、クエリが反復処理時に実行されることを意味します。ToList/ToArray は即時実行を引き起こします。
- IQueryable はリモートデータソース(データベース)用であり、IEnumerable はメモリ内のコレクション用です
📝 練習問題
int[] nums = { 34, 7, 21, 56, 12, 89, 3 }が与えられたとき、メソッド構文を用いて、20より大きいすべての偶数を降順で求め、結果を出力しなさい。- 生徒リスト(氏名、年齢、クラス)を定義し、LINQ を使用してクラスごとにグループ分けし、各クラスの生徒数を出力する
- 商品名と在庫数を格納する
Dictionary<string, int>を作成し、LINQ を使用して在庫数が 10 未満の商品名を検索し、List<string>に変換する - 遅延実行を示すコードを作成する:クエリを定義した後、元のコレクションを変更し、反復処理の結果に新しい要素が含まれているかどうかを確認する。その後、
ToList()を使用して、即時実行時の挙動と比較する。 - EF Coreにおいて、
IQueryableがIEnumerableよりも効率的な理由を、一文で説明してください。