1. はじめに
フィルタリングっていうのは、コレクションから特定の条件に合う要素だけを選ぶことだよ。例えば、データベースに10 000個の商品があって、マネージャーが「新しい条件に合う12個だけ欲しい」って言ってきたとする。毎回手動でループ書いて条件チェックするのは面倒だし、コードも読みにくくなる。LINQなら、そういう面倒を全部やってくれるんだ。
実際のアプリだと、フィルタリングは一番よく使うデータ操作だよ。ユーザーに必要なレコードだけ見せたり、コレクションからいらない要素を消したり、レポートや検索、メール送信用のデータ抽出とか。面接でもLINQのフィルタリングの質問はよく出るし、このテーマを理解してることは初心者.NETエンジニアの成功のカギだね。
2. Whereメソッドを知ろう
意味と動作原理
WhereメソッドはLINQの拡張メソッドで、フィルタ条件を決める関数(またはラムダ式)を受け取るよ。元のコレクションはそのままで、条件がtrueになる要素だけを含む新しいシーケンスを返すんだ。
基本のシグネチャ:
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
genericsとか難しそうな単語は気にしなくてOK。大事なのはざっくりしたイメージ:Whereは元のコレクションと、君が決めた条件を受け取る。実際は超シンプル:
- source — フィルタリングしたいコレクション(例:List<Product>)
- predicate — 関数/条件(例:p => p.Price > 100)
まずは簡単な例題
例えば、商品リストがあって、価格が100より高いものだけ選びたいとする。昔ならforeachループで手動で新しいリストに追加してたけど、LINQなら1行でできるよ。
3. フィルタリング
うちのProductクラスと初期コレクション
前のモジュールでこんな感じの商品クラスを書いたよね:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
// 画面表示用のToStringオーバーライド
public override string ToString()
{
return $"{Name} (価格: {Price})";
}
}
例えば、商品リストはこんな感じ:
var products = new List<Product>
{
new Product { Name = "パン", Price = 30 },
new Product { Name = "ミルク", Price = 87 },
new Product { Name = "チーズ", Price = 250 },
new Product { Name = "チョコレート", Price = 130 }
};
Whereで100円より高い商品をフィルタリング
var expensiveProducts = products.Where(p => p.Price > 100);
foreach (var product in expensiveProducts)
{
Console.WriteLine(product);
}
裏側で何が起きてるか:
- Whereがリストproductsの全要素を順番に見て、
- それぞれに関数(この場合はp => p.Price > 100)を実行、
- 関数がtrueを返したら、その商品は新しいコレクションに入る。
画面の出力結果:
チーズ (価格: 250)
チョコレート (価格: 130)
ポイント:元のproductsリストは変わってないよ!LINQはデータを壊さない。
4. 遅延フィルタリング(Deferred Execution)
面白くて大事なポイント:Whereの結果は、実際に要素にアクセスしたときだけ計算される。例えばforeachループが始まった瞬間、LINQが元のコレクションを走査して、その場でフィルタリングする。結果を使わなければ何も起きない。これを遅延実行(deferred execution)って呼ぶよ。
これにはいろんなメリットがある:
- メソッドチェーンをいくらでも長くできる(例えばフィルタ+ソートとか)。最初に結果を使うときだけ全部実行される。
- メモリ節約:余計な中間コレクションを作らない。
- 必要なら実行をキャンセルしたり、必要な数だけで止めたりできる。
図でイメージ
元のコレクション —► Where (条件) —► 必要な要素だけ走査
[ × ] [ × ] [ ○ ] [ ○ ] —► p.Price > 100 —► [ ○ ] [ ○ ]
(× — 条件に合わない要素、○ — 合う要素)
5. 書き方いろいろ
クラシックなメソッド構文(Method Syntax)
この書き方はもう使ってるね:
var result = products.Where(p => p.Price > 100);
もう一つ:クエリ構文(Query Syntax)
LINQはSQLっぽい書き方もできるよ:
var result = from p in products
where p.Price > 100
select p;
結果は同じ!
どっちを使うかは好みだけど、実際のプロジェクトや長いメソッドチェーンではmethod syntax(ドットでつなぐやつ)がよく使われるよ。
6. 複雑なフィルタ条件
複数条件
論理演算子(&&, ||, !)も使える:
// 高い商品で「チョコレート」以外を探す
var filtered = products.Where(
p => p.Price > 100 && p.Name != "チョコレート"
);
文字列でフィルタ(部分一致、大文字小文字、Contains)
// 名前に「ル」が入ってる商品
var lProducts = products.Where(p => p.Name.Contains("ル"));
注意:Containsは大文字小文字を区別するよ!無視したいならこう:
var lProducts = products.Where(p => p.Name
.ToLower().Contains("ル")); // これで「ミルク」と「チョコレート」両方ヒットする
複数コレクションでフィルタ
例えば、商品リストと支払いリストがあるとする。支払いリストにある商品だけフィルタしたい場合は、AnyやJoinメソッドを使うのがベター(このへんはまた後で説明するね)。とりあえず、LINQはもっと複雑なフィルタもできるって覚えておこう!
7. 便利な小ネタ
ネストしたフィルタやメソッドチェーン
Whereを何回もつなげて、複数段階でフィルタできる:
var filtered = products
.Where(p => p.Price > 100)
.Where(p => p.Name.StartsWith("チ"));
でも、条件は1つのWhereにまとめて論理演算子で書いた方が効率的だし、コードもスッキリするよ。
組み込みコンパレータやカスタム関数
標準の演算子だけじゃ足りないときもある。例えば、カルチャや大文字小文字を無視して文字列をフィルタしたい場合は、比較メソッドを使うと便利:
var rusProducts = products.Where(
p => p.Name.StartsWith("チ", StringComparison.OrdinalIgnoreCase)
);
8. フィルタとユーザー入力
実際に簡単なコマンドラインフィルタを作ってみよう!例えば、ユーザーに最小価格を聞いて、それ以上の商品を全部表示する。
Console.Write("商品の最小価格は? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
var filtered = products.Where(p => p.Price >= minPrice);
foreach (var product in filtered)
{
Console.WriteLine(product);
}
}
else
{
Console.WriteLine("エラー:数字じゃない値が入力されたよ。");
}
これで「ミニアプリ」もほぼお店っぽくなったね!
9. Whereでよくあるミスや落とし穴
プログラミングも人生も、落とし穴はつきもの。ここは注意しよう。
ToList()やToArray()の呼び忘れ
Whereの結果はリストでも配列でもない!IEnumerable<Product>オブジェクトだよ。foreachループではそのまま使えるけど、コレクション(例えばインデックスアクセスしたいとき)は.ToList()や.ToArray()を忘れずに:
var filteredList = products.Where(p => p.Price > 100).ToList();
これを忘れると、"列挙中にコレクションが変更されました"みたいなエラーが出やすい。特に、ループ中に元のコレクションを変更した場合は要注意。
元のコレクションの変更
LINQのフィルタは遅延実行だから、ループ中に元のリストから要素を削除すると例外が出ることがある。特にマルチスレッドや、フィルタ中にコレクションをいじる場合は注意してね。
null値とプレディケート
コレクションにnullが入ってて、プレディケートでフィールドにアクセスするとNullReferenceExceptionになる。例えば:
var filtered = products.Where(p => p.Name.StartsWith("ア"));
もしproductsの中にnullがあったら、コードが落ちる。
アドバイス:nullが混じってる可能性があるなら、必ずnullチェックを入れよう:
var filtered = products.Where(p => p != null && p.Name.StartsWith("ア"));
GO TO FULL VERSION