1. はじめに
例えば、図書館に行って「2020」年以降に出版されたプログラミングの本を全部、著者名順で探したいとする。いちいち棚を全部見て本を一冊ずつチェックするなんてやらないよね?普通は、何がどこにあるか知ってる司書さんに頼むでしょ。
LINQ(Language Integrated Query、つまり「言語に統合されたクエリ」)は、まさにその「賢い司書」みたいな存在で、C#の中にある「SQLエンジン」なんだ!(つまり、データにクエリを書くためのツール)
LINQは、データから何が欲しいかを「説明」するための言語だと思って!どうやって取るかは気にしなくていい。例えば「最初の要素を取って、条件をチェックして、合ってたら新しいリストに追加して、次に進んで…」みたいな命令じゃなくて、「値段が1000より高い商品を全部ちょうだい」って言うだけ。あとはC#が一番効率よくやってくれる。
LINQのキーポイント: IEnumerable<T>インターフェースを実装してるどんなデータソースにも、標準のクエリ構文を提供してくれるってこと。これが最高なのは、List<T>や配列、HashSet<T>とか、みんなIEnumerable<T>を実装してるから。もしデータがDBにあっても、Entity FrameworkみたいなライブラリがLINQクエリを本物のSQLに変換してくれる!
LINQはC#に10年以上前からある。これが登場したことで.NETでのデータ操作のやり方がガラッと変わったんだよね。LINQ以前は、コレクションのフィルタやソート、変換のために毎回同じようなコードをいっぱい書かなきゃいけなかったし、SQLクエリを文字列で書くしかなかった(しかもコンパイラがチェックしてくれないから、実行時エラーになりやすかった)。LINQは「クエリ」という考え方をプログラミング言語の中に持ち込んで、型安全で読みやすくしてくれたんだ。
LINQは、Generics(ジェネリクス)が登場して以来、C#で一番すごい進化だって言う人も多いよ。
2. LINQのメリット:みんなが好きな理由
コードが短くて読みやすい: 例えば、値段が1000より高い商品を大きなDBからフィルタしたい時、前は何行もコードを書かなきゃいけなかった。でもLINQなら1行でOK。コードが短い=バグも減るし、読みやすくて分かりやすい。自然言語に近い感じになるよ。
プログラマーのジョーク:「書くコードが少なければ少ないほど、バグを仕込む場所も減る」LINQはまさにそれを助けてくれる!
パワフルで柔軟: LINQはめっちゃたくさんの操作ができる。フィルタ(Where)、変換(Select)、ソート(OrderBy)、グループ化(GroupBy)、集計(Sum, Average)、結合(Join)とか色々。これらを組み合わせて、複雑なデータ処理もサクッとできる。
型安全: これ、めっちゃ大事!SQLクエリを文字列で書くと、コンパイラは何も分からない。カラム名を間違えても、実行時に落ちて初めて気づく。でもLINQなら、C#のコンパイラがクエリをチェックしてくれる。例えばProductに存在しないフィールドをクエリしようとしたら、すぐにエラーを出してくれる。まるで自分専用の校正者がいて、他の人に見られる前にタイプミスを全部見つけてくれる感じ!
言語との統合: LINQクエリは「魔法の文字列」じゃなくて、ちゃんとしたC#の構文。おなじみのラムダ式やデータ型を使うから、LINQへの移行もスムーズ。
遅延実行(Deferred Execution): これ、LINQの中でも一番クールで、ちょっと難しいけど超重要なコンセプト。LINQクエリはすぐには実行されない。書いた時点では「組み立て」だけで、実際に結果が欲しくなるまで(例えばforeachで回すまで)待ってる。これのおかげで、大量データでも無駄な処理をしないで済む。詳しくはレクチャー166で話すけど、とりあえず「LINQは賢いから、余計なことはしない」って覚えておいて!
汎用性と拡張性: LINQはメモリ上のリストだけじゃない。いろんな「プロバイダー」があるんだ:
- LINQ to Objects:メモリ上のコレクション(List<T>、配列など)用。
- LINQ to SQL / Entity Framework Core:SQLデータベース用(LINQクエリがSQLに変換される)。
- LINQ to XML:XMLドキュメント操作用。
- 他にもいろいろ。
つまり、LINQを覚えれば、どんなデータソースでも同じクエリロジックで扱えるってこと!
3. 「昔ながら」のコレクション操作の問題点
LINQがなかった頃、C#でコレクションを処理するコードはこんな感じだった:
var products = new List<Product> { /* ... */ };
var expensive = new List<Product>();
foreach (var prod in products)
{
if (prod.Price > 100)
expensive.Add(prod);
}
expensive.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var item in expensive)
Console.WriteLine(item.Name);
こういうやり方は典型的で、まず手動でフィルタして、次にソートして…って感じで中間リストも作る。ロジックが増えるほど、コードも変数も増えてバグも増える。まるで家具のパーツはあるけど、全部手作業で組み立てるみたいな感じで、めんどくさい!
ループの国のアリス
例えば、めっちゃ大きいユーザーリストがあって、「18」歳以上でネオンビルに住んでる人の名前だけをアルファベット順で3人だけ出したいとする。ネストしたループや条件分岐、ソート、中間リスト…って色々書かないといけない。でもLINQなら一発!
4. LINQクエリってどんな感じ?簡単な例
LINQには2つの基本的な構文があるよ:
- Method Syntax(メソッドチェーン型)
- Query Syntax(SQLっぽい構文)
最近のプロジェクトではMethod Syntaxがよく使われてる。アプリの例で見てみよう:
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name)
.Take(3);
foreach (var name in expensive)
Console.WriteLine(name);
めっちゃ分かりやすいでしょ?フィルタ、ソート、フィールド選択、件数制限。最小限のコードで最大限の意味!
同じことをQuery Syntaxで書くと
var expensive = from p in products
where p.Price > 100
orderby p.Price
select p.Name;
foreach (var name in expensive.Take(3))
Console.WriteLine(name);
どっちも同じ結果になるから、好きな方を使ってOK(でもMethod Syntaxの方がよく使われる)。
5. LINQのアーキテクチャ:中身はどうなってる?
LINQは、IEnumerable<T>(またはIQueryable<T>、これは後で説明する)を実装してるコレクションなら何でも使える。
LINQの主なコンポーネント
| コンポーネント | 簡単な説明 |
|---|---|
| LINQ拡張クラス | 静的メソッド(、、など)がクラスにある |
| デリゲート | よく使うのはや |
| 遅延実行 | クエリは最初に列挙(例えば)される時だけ実行される |
| クエリプロバイダー | (LINQ to SQLやLINQ to Entities用)— メソッドチェーンをSQLクエリなどに変換する |
ビジュアル図
コレクション(List<Product>、T[]、...)
│
▼
LINQメソッド(Where、OrderBy、Select…)
│
▼
クエリ(IEnumerable<T>)
│
▼
実際の実行(foreach、ToList、Count、…)
6. 例:アプリでLINQチェーンを分解してみよう
Productクラスを使ったミニアプリに戻るよ:
// 商品クラス
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
商品リストはこんな感じ:
var products = new List<Product>
{
new Product { Name = "チーズ", Price = 250.5 },
new Product { Name = "パン", Price = 30 },
new Product { Name = "ミルク", Price = 80 },
new Product { Name = "コーヒー", Price = 330 },
new Product { Name = "バター", Price = 140 }
};
例えば、「値段が100ユーロより高い商品の名前を全部、値段順で表示」が課題だとする。
昔のやり方
var filtered = new List<Product>();
foreach (var p in products)
{
if (p.Price > 100)
filtered.Add(p);
}
filtered.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var p in filtered)
Console.WriteLine(p.Name);
LINQのやり方
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name);
foreach (var name in expensive)
Console.WriteLine(name);
1行目で「Price > 100のやつをフィルタして、値段順に並べて、名前だけ取る」って課題の本質をそのまま書いてる。
7. よく使うLINQ操作と「昔ながら」のやり方の比較
| 操作 | 昔のやり方 | LINQ |
|---|---|---|
| フィルタ | foreach + if + Add | |
| 変換(フィールド選択) | foreach + Add(field) | |
| ソート | Sort(comparer) | , |
| ユニーク値 | foreach + contains + Add | |
| カウント | foreach + counter++ | , |
| 条件チェック | foreach + if | , |
| 最初/最後の検索 | foreach + if + break | , , |
| 件数制限 | foreach + カウンタ + break | , |
8. 実践:アプリにLINQを追加してみよう
アプリの基本コード(Productリスト)を使って、LINQでいくつか便利な操作をやってみよう。
フィルタとソート
// 200ユーロより安い商品を名前順で選ぶ
var cheapProducts = products
.Where(p => p.Price < 200)
.OrderBy(p => p.Name);
foreach (var p in cheapProducts)
Console.WriteLine($"{p.Name}: {p.Price}ユーロ");
コレクションの変換(プロジェクション)
// 値段(double)のリストを取得
var prices = products.Select(p => p.Price);
foreach (var price in prices)
Console.WriteLine(price);
最初に合う要素の検索
// 名前が「コ」で始まる最初の商品
var firstK = products.FirstOrDefault(p => p.Name.StartsWith("コ"));
Console.WriteLine(firstK?.Name ?? "見つからなかった");
100ユーロより高い商品の数を数える
int count = products.Count(p => p.Price > 100);
Console.WriteLine($"そんな商品は: {count}個。");
このレクチャーから、コレクションの操作やフィルタ、必要なデータの選択、集計などでLINQをガンガン使っていくよ。LINQの新しいメソッドや.NET 9の新機能も今後のレクチャーで解説するから、まずは簡単なクエリでLINQのパワーを体感してみて!
GO TO FULL VERSION