IEnumerableとIQueryableの相互変換は可能だ。その意味について考えてみる。
var r = new Random(); // 1: IEnumerable<int>をIQueryable<int>に変換する IEnumerable<int> randomDatas = from index in Enumerable.Range(0, 100000) select r.Next(); IQueryable<int> queryableIntDatas = randomDatas.AsQueryable(); // 2: IQueryable<Customer>をIEnumerable<Customer>に変換する using (var context = new AdventureWorksLT2012_DataEntities()) { IQueryable<Customer> queryableCustomers = from customer in context.Customer select customer; IEnumerable<Customer> enumerableCustomer = queryableCustomer.AsEnumerable(); // IEnumerable<Customer> enumerableCustomer = (IEnumerable<Customer>)queryableCustomer; }
先に2番目の変換を見てみる。
IQueryable<T>は、IEnumerable<T>を継承している。そのため、2番目の変換は、単にキャストするのと変わらない(AsEnumerableの実装はキャストしているだけだ)。
IEnumerable<T>にキャストしてforeach等で列挙した場合、IQueryable<T>の実装が持つGetEnumerator()を呼び出していることになる。これは擬似的に、以下のようなコードだ。
// queryableの列挙子を取得する擬似コード public static IEnumerable<T> PseudoAsEnumerable<T>(this IQueryable<T> queryable) { // queryableで示されるクエリを実行し、結果群が返される(GetEnumeratorの呼び出し) foreach (var value in queryable) { yield return value; } }
queryableは普通の列挙子ではない。LINQ to SQLやLINQ to Entitiesで示される、何らかのストレージとのインターフェイスを抽象化したものだ。そのため、このforeachによって何が引き起こされるのかは、queryableが何を対象にしているのかによって異なる。LINQ to SQLやLINQ to Entitiesでは、RDBに対してSQL文が発行され、結果が返って来るだろう。そのレコード群がforeachで列挙される。
では1番目の変換はどうだろう。
randomDatasはLINQ to Objectsのクエリ式だ。IQueryableで表現するためには、このクエリ全体が「記述的」になる必要がある。つまり、randomDatasクエリ式を「呼び出す式」がIQueryableで表現される。
IQueryableがGetEnumeratorによって列挙される時、randomDatasクエリ式を解釈する「LINQプロバイダー」が動作し、プロバイダーがrandomDatasクエリ式を列挙する。
当然ながら、この例ではRDBにアクセスするわけではないので、queryableIntDatasは「SQL文」を表している訳ではない。以下はデバッガのスナップショットだ。
IQueryableインターフェイスには、「Expression」プロパティが存在する。このプロパティは、例の「Expression」だ。IQueryableが表現するクエリ式全体が、このExpressionプロパティから公開される。そこで、このExpressionがどうなっているのか、Expressionの探索で示したFlatDumpExpressionVisitorを使ってXMLにダンプすると、以下のような単一のノードが返される。
<Constant value="System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Int32,System.Int32]" />
結局、IQueryableが指す参照先は、IEnumerableのベールに包まれたrandomDatasであり、固定値(Constant)として認識されている。同じIQueryableを基礎としているのに、これは明らかにLINQ to SQLやLINQ to Entitiesとは異なる。
この違いは、LINQソースから生まれる。LINQ to Entitiesの例では、AdventureWorksLT2012_DataEntitiesクラスのCustomerプロパティからクエリを開始している。AsQueryableは、LINQ to Objectsのクエリを変換する。この違いによって、IQueryableの評価を受け持つ「LINQプロバイダー」が異なり、SQL文に変換されたり、列挙子の呼び出しに変換されたりする。