LINQ 大好きなのはいいんだけど基本的に for で回してるのと同じだってことを認識していない人が多くて困る。魔法か何かと勘違されてる。これに起因してパフォーマンス悪化しまくり。LINQ 便利で使うのだけど、LINQ がない時にそんなにループで回すこと多かったかな?という疑問。隠蔽されることで、”とりあえず List” にしといて LINQ で取ればいいやで実装する人がやけに多い。その都度注意するのだけど分かったのか分かってないのか減らない。メソッドで引数を受けるときも IEnumerable
で受ければいいのに List
をにしてるからわざわざ ToList
が必要だったり、必要ないのに ToList
したりなかなかのカオス。
実際どういう動きになるのかを改めて確認してみる。
Where → Select したものを Any と foreach で呼び出す
Any
or foreach
を使うたびに LINQ が評価される。
Where を評価するための判断材料の生成コストや、Select で変換するためのコストが高いとその分パフォーマンスが悪化することになる。
コード
var list = new List<string>(){ "0", "1", "2", "3", "4" }; var targets = list.Where(v => { Console.WriteLine("Where => " + v); return true; }).Select(v => { Console.WriteLine("Select => " + v + "\n"); return v; }); Console.WriteLine("----- Any -----"); targets.Any(); Console.WriteLine("----- foreach -----"); foreach (var target in targets) { }
出力
----- Any ----- Where => 0 Select => 0 ----- foreach ----- Where => 0 Select => 0 Where => 1 Select => 1 Where => 2 Select => 2 Where => 3 Select => 3 Where => 4 Select => 4
foreach で止めてからの foreach
当たり前だけど?頭から再度回る。
コード
var list = new List<string>(){ "0", "1", "2", "3", "4" }; var targets = list.Where(v => { Console.WriteLine("Where => " + v); return true; }).Select(v => { Console.WriteLine("Select => " + v + "\n"); return v; }); Console.WriteLine("----- foreach 1 -----"); foreach (var target in targets.Select((v, i) => new { v, i })) { if (target.i == 3) { Console.WriteLine("Break"); break; } } Console.WriteLine("----- foreach 2 -----"); foreach (var target in targets.Select((v, i) => new { v, i })) { }
出力
----- foreach 1 ----- Where => 0 Select => 0 Where => 1 Select => 1 Where => 2 Select => 2 Where => 3 Select => 3 Break ----- foreach 2 ----- Where => 0 Select => 0 Where => 1 Select => 1 Where => 2 Select => 2 Where => 3 Select => 3 Where => 4 Select => 4
List の ToList()
自身を返すと思いきや別インスタンスの List
public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { if (source == null) throw Error.ArgumentNull("source"); return new List<TSource>(source); }
まとめ
現実問題としては LINQ による遅延評価のメリットを出す機会よりも、抽出した内容に対して複数の絞り込みをするケースが多いために ToList
をせざるをえない機会が多い。そうなってしまうのはそもそものデータ構造やロジックの問題なので根が深そう。
試した環境は mac だけど、 brew install mono
で入るし vim で quickrun 使えば即実行できるし便利。