C#のIEnumerable<T>は使い方は「列挙可能」を表すインタフェースであり、そのための操作を提供する。このインタフェースは理解度によっては想定していない挙動を引き起こすため、注意が必要だ。実際に私も3度ばかり仕事のコードでやらかしたことがある。

IEnumerable<T>はその名前からは判断つきにくいが、ふたつの機能を提供する。これは便利な反面問題も引き起こすことになる。

  1. 列挙可能であること
  2. 遅延評価をすること

列挙可能とは

IEnumerable<T>は複数のT型からなるデータを列挙可能であることを表す。これはなんらかのT型の集合から順番に取り出すことができることを示す。つまり、IEnumerable<T>が実装されていれば、それはforeach文の対象にもできるし、Linq式として扱うことができる。C#のコンセプトからすれば、集合に対するクエリが発行できることに等しい。

遅延評価のおまけつき

しかし、IEnumerable<T>は列挙可能にとどまらず、その操作は遅延評価される。例えば次のようなコードを書いたとしよう。

var a = new List<int> { 1, 2, 3, 4 };
var b = a.Select(x => x * 2);
var c = a.Select(x => x * 2).ToList();

ここで、b はIEnumerable<T>であり、b を起点とした操作が行われるたびにSelect()に渡されたラムダ式が実行される。一方で c は最後にリストにしているのでList<T>であり、複数回ラムダ式が実行されることはない。

通常のコードでは遅延評価されたとしても結果が変わらないことが多いが、それでも期待を裏切られる次のようなコードもある。

var d = a.Select(_ => Guid.NewGuid());

これは d を評価するたびに別のGuid(UUID)を生成するので、複数回 d を使うのであれば期待した結果にならない。遅延評価は実際に必要になるまで評価しないので、すべての要素を使わないのであればパフォーマンスの観点で有利になることも多いが、それによってロジックが依然期待したものであるかどうかは十分に留意したい。

IQueryable<T>

IQueryable<T>はEntity Framework Coreで利用されるIEnumerable<T>のデータベースクエリ版だ。データベースクエリに変換する都合でどのような式でも記述できるわけではなく、IEnumerable<T>よりも制約が強いが、概ね同じように利用できる。

また、IQueryable<T>はIEnumerable<T>に変換することもでき、クエリの結果をメモリ上に移す、すなわちデータベースからレコードを取得することになる。いくつかの質問サイトではIQueryable<T>で発行できないクエリをIEnumerable<T>に変換するといいといった回答を見るが、それは実際には誤りで、本来はサードパーティ製ライブラリやExpressionによってIQueryable<T>を拡張しなければならない。

Entity Framework Coreはバカであるもあわせて見てほしい。