Enumerable.Where()
| 対応: | C# 3.0(2007) |
|---|
シーケンス(配列やコレクション)から条件に一致する要素だけを抽出する LINQ の拡張メソッドです。
構文
using System.Linq; // ラムダ式で条件を指定して要素を絞り込む IEnumerable<T> result = source.Where(x => 条件式); // インデックス付きオーバーロード(要素と位置の両方が使えます) IEnumerable<T> result = source.Where((x, i) => 条件式);
メソッド一覧
| メソッド | 概要 |
|---|---|
| Where(predicate) | 条件に一致する要素のみを返す遅延評価のシーケンスを返します。 |
| Where((x, i) => ...) | 要素とそのインデックスを使った条件で絞り込みます。 |
| OfType<T>() | 指定した型に変換できる要素だけを抽出します。 |
サンプルコード
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 偶数だけを抽出
IEnumerable<int> evens = numbers.Where(n => n % 2 == 0);
Console.WriteLine(string.Join(", ", evens)); // 2, 4, 6, 8, 10
// 5 より大きい要素を抽出
IEnumerable<int> bigOnes = numbers.Where(n => n > 5);
Console.WriteLine(string.Join(", ", bigOnes)); // 6, 7, 8, 9, 10
// 複数条件を組み合わせる(偶数かつ 5 より大きい)
IEnumerable<int> filtered = numbers.Where(n => n % 2 == 0 && n > 5);
Console.WriteLine(string.Join(", ", filtered)); // 6, 8, 10
// 文字列リストから特定の条件で絞り込む
List<string> members = new List<string> { "Okabe Rintaro", "Makise Kurisu", "Shiina Mayuri", "Hashida Itaru" };
IEnumerable<string> mMembers = members.Where(m => m.StartsWith("S"));
Console.WriteLine(string.Join(", ", mMembers)); // Shiina Mayuri
// インデックス付きオーバーロードで偶数インデックスの要素だけ取得
IEnumerable<int> evenIdx = numbers.Where((n, i) => i % 2 == 0);
Console.WriteLine(string.Join(", ", evenIdx)); // 1, 3, 5, 7, 9
// クエリ構文(SQL ライク)でも同じことができる
IEnumerable<int> query = from n in numbers
where n % 2 == 0
select n;
Console.WriteLine(string.Join(", ", query)); // 2, 4, 6, 8, 10
dotnet script enumerable_where.csx 2, 4, 6, 8, 10 6, 7, 8, 9, 10 6, 8, 10 Shiina Mayuri 1, 3, 5, 7, 9 2, 4, 6, 8, 10
クエリ構文(from ... where ... select)とメソッド構文(.Where())はどちらを使っても同じ結果になります。メソッド構文は他のLINQメソッド(Select()・OrderBy()・GroupBy() など)と連結しやすく、IDEの補完も効くためメソッド構文もよく使われます。クエリ構文はSQLに慣れた人が読みやすい場合に使われます。
よくあるミス1: 遅延評価の罠
『Where()』は遅延評価のため、呼び出した時点ではフィルタリングが実行されません。元のコレクションが変更されると、フィルタ結果にも影響します。
enumerable_where_lazy_ng.csx
using System;
using System.Collections.Generic;
using System.Linq;
List<string> members = new List<string> { "Okabe Rintaro", "Makise Kurisu", "Shiina Mayuri" };
IEnumerable<string> result = members.Where(m => m.StartsWith("O"));
Console.WriteLine(result.Count()); // 1
members.Add("Okabe Rintaro (beta)");
Console.WriteLine(result.Count()); // 2(追加した要素もフィルタに引っかかる)
dotnet script enumerable_where_lazy_ng.csx 1 2
『ToList()』で実体化すれば、その時点のスナップショットになります。後からコレクションが変更されても影響を受けません。
enumerable_where_lazy_ok.csx
using System;
using System.Collections.Generic;
using System.Linq;
List<string> members = new List<string> { "Okabe Rintaro", "Makise Kurisu", "Shiina Mayuri" };
List<string> snapshot = members.Where(m => m.StartsWith("O")).ToList();
Console.WriteLine(snapshot.Count); // 1
members.Add("Okabe Rintaro (beta)");
Console.WriteLine(snapshot.Count); // 1(ToList()した時点で固定)
dotnet script enumerable_where_lazy_ok.csx 1 1
よくあるミス2: nullによるNullReferenceException
シーケンス内に null が含まれる場合、プロパティアクセスで NullReferenceException が発生します。null チェックを条件に含めることで NullReferenceException を防げます。
enumerable_where_null_ok.csx
using System;
using System.Collections.Generic;
using System.Linq;
List<string> withNull = new List<string> { "Okabe", null, "Kurisu" };
// m != null を先に書くことで null.Length を回避
var safe = withNull.Where(m => m != null && m.Length > 4);
Console.WriteLine(string.Join(", ", safe));
dotnet script enumerable_where_null_ok.csx Okabe, Kurisu
実践パターン
sample_enumerable_where_practical.csx
using System;
using System.Collections.Generic;
using System.Linq;
// キャラクターデータクラス
class Character {
public string Name { get; set; }
public string Org { get; set; }
public int Age { get; set; }
}
var characters = new List<Character> {
new Character { Name = "Okabe Rintaro", Org = "Future Gadget Lab", Age = 18 },
new Character { Name = "Makise Kurisu", Org = "Victor Chondria", Age = 18 },
new Character { Name = "Shiina Mayuri", Org = "Future Gadget Lab", Age = 16 },
new Character { Name = "Hashida Itaru", Org = "Future Gadget Lab", Age = 19 },
new Character { Name = "Amane Suzuha", Org = "Braun Tube Workshop", Age = 18 },
};
// パターン1: Where → Select → ToList のチェーン
var names = characters
.Where(c => c.Org == "Future Gadget Lab")
.Select(c => c.Name)
.ToList();
Console.WriteLine(string.Join(", ", names));
// Okabe Rintaro, Shiina Mayuri, Hashida Itaru
// パターン2: Where → OrderBy → Take(上位N件)
var topTwo = characters
.Where(c => c.Age >= 18)
.OrderBy(c => c.Name)
.Take(2)
.ToList();
Console.WriteLine(string.Join(", ", topTwo.Select(c => c.Name)));
// Amane Suzuha, Hashida Itaru
// パターン3: Where でグループ化前の絞り込み
var countByOrg = characters
.Where(c => c.Age >= 18)
.GroupBy(c => c.Org)
.Select(g => new { Org = g.Key, Count = g.Count() });
foreach (var item in countByOrg) {
Console.WriteLine(item.Org + ": " + item.Count);
}
dotnet script enumerable_where_practical.csx Okabe Rintaro, Shiina Mayuri, Hashida Itaru Amane Suzuha, Hashida Itaru Future Gadget Lab: 2 Braun Tube Workshop: 1 Victor Chondria: 1
概要
『Where()』は遅延評価(Lazy Evaluation)で動作するため、メソッドを呼び出した時点ではシーケンスが実体化されません。『ToList()』や『foreach』など実際に列挙が行われたときに初めて処理が走ります。
『Where()』はフィルタリングのみを行い、元のシーケンスは変更されません。抽出した結果をリストとして使いたい場合は『ToList()』に続けて呼び出してください。
複数の条件を組み合わせるときは『Where()』を連鎖(チェーン)させることもできます。要素を変換しながら絞り込みたい場合は『Enumerable.Select()』と組み合わせて使うことができます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。