C#: LINQ クエリ

1. LINQの概念と背景

LINQ(Language Integrated Query)は、C# に組み込まれたデータクエリ言語であり、統一された構文を使用してさまざまなデータソース(コレクション、データベース、XML など)に対してクエリを実行することができます。LINQ が登場する前は、異なるデータソースに対してクエリを実行するには、それぞれ異なる API を習得する必要がありましたが、LINQ はこの問題を解決しました。

▶ サンプル

CSHARP
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);
}
▶ 試してみよう
TEXT 📖 参照専用
12
21
15

2. クエリ構文とメソッド構文

LINQ には、「クエリ構文」と「メソッド構文」の 2 つのスタイルがあります。両者は機能的に同等です。メソッド構文は、より多くの演算子をサポートし、簡潔な連鎖呼び出しが可能であるため、実際にはより一般的に使用されています。

▶ サンプル

CSHARP
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));
▶ 試してみよう
TEXT 📖 参照専用
Query syntax: 12, 15, 21
Method syntax: 12, 15, 21

3. from / where / select / order by / group

クエリ構文の中核となる句:fromはデータソースと範囲変数を指定し、whereはフィルタリングを行い、selectは結果を投影し、orderbyはソートを行い、group ... byはグループ化を行います。

▶ サンプル

CSHARP
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);
}
▶ 試してみよう
TEXT 📖 参照専用
Amy
Anna
Alice

(1) GROUP BY の例

CSHARP
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)");
}
TEXT 📖 参照専用
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 コレクションへの変換

▶ サンプル

CSHARP
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)));
▶ 試してみよう
TEXT 📖 参照専用
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[] も含まれます。

▶ サンプル

CSHARP
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));
▶ 試してみよう
TEXT 📖 参照専用
List query: BANANA, CHERRY
Array query: 95, 88, 85

6. 辞書を使ったLINQ

辞書でLINQを使用する場合、各要素はKeyValuePair<TKey, TValue>であり、.Keyおよび.Valueを介してアクセスされます。

▶ サンプル

CSHARP
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"]);
▶ 試してみよう
TEXT 📖 参照専用
Grape: 12.0
Orange: 7.8
Apple: 5.5
Doubled orange price: 15.6

7. 遅延実行と即時実行

LINQ クエリはデフォルトで遅延実行されます。つまり、クエリを定義しても実行は行われず、結果を反復処理する(例:foreachを使用する場合など)ときに初めてクエリが実行されます。 ToList()ToArray()Count()、またはFirst()のようなメソッドを呼び出すと、即座に実行が開始されます。

▶ サンプル

CSHARP
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);
}
▶ 試してみよう
TEXT 📖 参照専用
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などのクエリ言語に変換し、サーバー側で実行することができます。

▶ サンプル

CSHARP
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);
▶ 試してみよう
TEXT 📖 参照専用
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はまずすべてのデータをメモリに読み込んでからフィルタリングを行います。このため、パフォーマンスの差は大きくなる可能性があります。

❓ よくある質問

Q クエリ構文とメソッド構文、どちらが優れていますか?
A メソッド構文の方が一般的に広く使われており、すべての演算子をサポートし、簡潔な連鎖呼び出しが可能です。クエリ構文は一部の演算子しかサポートしていませんが、複雑な結合やグループ化処理を行う場合、可読性が高くなる場合があります。
Q FirstOrDefault と First の違いは何ですか?
A First は要素が見つからない場合に例外をスローしますが、FirstOrDefault はデフォルト値(参照型の場合は null、値型の場合は 0)を返します。ほとんどの場合、FirstOrDefault を使用することをお勧めします。
Q 「Single」と「First」の違いは何ですか?
A 「Single」は、シーケンスに一致する要素が正確に1つだけ存在することを要求します。一致する要素が2つ以上ある場合は例外を発生させます。「First」は、単に最初の一致する要素を返します。
Q LINQの結果を2回反復処理すると、クエリが2回実行されてしまうのはなぜですか?
A 遅延実行のため、各反復処理で再計算が行われるからです。ToList() を使用して結果をキャッシュし、冗長な計算を回避してください。
Q IQueryable と IEnumerable のどちらを選べばよいですか?
A データベースクエリには IQueryable を使用し、SQL をサーバー側で実行するようにします。メモリ内のコレクションには IEnumerable を使用します。

📖 まとめ

📝 練習問題

  1. int[] nums = { 34, 7, 21, 56, 12, 89, 3 }が与えられたとき、メソッド構文を用いて、20より大きいすべての偶数を降順で求め、結果を出力しなさい。
  2. 生徒リスト(氏名、年齢、クラス)を定義し、LINQ を使用してクラスごとにグループ分けし、各クラスの生徒数を出力する
  3. 商品名と在庫数を格納するDictionary<string, int>を作成し、LINQ を使用して在庫数が 10 未満の商品名を検索し、List<string>に変換する
  4. 遅延実行を示すコードを作成する:クエリを定義した後、元のコレクションを変更し、反復処理の結果に新しい要素が含まれているかどうかを確認する。その後、ToList()を使用して、即時実行時の挙動と比較する。
  5. EF Coreにおいて、IQueryableIEnumerableよりも効率的な理由を、一文で説明してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%