結局IEnumerable<T>とIQueryable<T>はどう違うの？


# はじめに

こんにちは。私は社会人2年目C#er。初心者です。

EntityFrameworkなどが用意するデータモデルは`IQueryable<T>`型を返しますが、同様にコレクションを扱う`IEnumerable<T>`型とどう異なっているのかよくわかっていませんでした。

[このStackOverFlowの回答](https://stackoverflow.com/questions/252785/what-is-the-difference-between-iqueryablet-and-ienumerablet)や[こちらの記事](https://samueleresca.net/2015/03/the-difference-between-iqueryable-and-ienumerable/)を読むことで違いの概要は把握することができたのですが、.NETの実装も踏まえた詳細を理解したかったのでコードを書きながら調査しました。

本記事に関連するサンプルコードは私のGitHub上の[こちら](https://github.com/momotaro98/csharp-sandbox/tree/master/IQueryableBehaviours)にあがっています。

# TL;DR

IQueryableはIEnumerableのインターフェースを継承していて、foreach時やToListしたときの"振る舞い"は同じ。

そもそもIQueryableは外部データソース上を扱うLinqプロバイダのために用意されている。

`IEnumerable<T>`はクエリ発行がプログラム内部のインメモリ上。一方で、`IQueryable<T>`のクエリ発行は外部リソースのプログラム上(RDBMSやAPIサーバ)。

この違いは.NET実装上では`Where`などのLinqメソッドにて確認することができる。

# 作って比較してみる

動作を比較するために`IEnumerable<T>`と`IQueryable<T>`を実装してみます。

本記事では両者のLinqメソッド実行時の様子を比較するので直接関係の無い部分は作り込んでいません。

## `IEnumerable<T>`の実装

```c#:MyEnumerable.cs
public class MyEnumerable : IEnumerable<int>
{
    public IEnumerator<int> GetEnumerator()
    {
        return this.ReturnEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    private IEnumerator<int> ReturnEnumerator()
    {
        var intArray = new int[] {4, 8, 7, 0, 6, 5, 1, 9, 3, 2};
        foreach (var val in intArray)
        {
            yield return val;
        }
     }
}
```

本サンプルコードでは`IEnumerable<int>`を対象としイテレータ(`IEnumerator`)もint型対応です。
`yield return`を持つメソッドをイテレータとしています。

## `IQueryable<T>`の実装

[こちらの記事](https://ufcpp.net/study/csharp/sp3_iqueryable.html)を参考に実装しました。

以下のクラス`Query`は`IQueryable<T>`を直接実装するクラスです。

```c#:Query.cs
public class Query<T> : IQueryable<T>, IQueryable, IEnumerable<T>, IEnumerable, IOrderedQueryable<T>, IOrderedQueryable
{
    QueryProvider provider;
    Expression expression;
    public Query(QueryProvider provider)
    {
        if (provider == null)
        {
            throw new ArgumentNullException("provider");
        }
        this.provider = provider;
        this.expression = Expression.Constant(this);
    }
    
    public Query(QueryProvider provider, Expression expression)
    {
        if (provider == null) {
            throw new ArgumentNullException("provider");
        }
        if (expression == null) {
            throw new ArgumentNullException("expression");
        }
        this.provider = provider;
        this.expression = expression;
    }
    
    Expression IQueryable.Expression
    {
        get { return this.expression; }
    }
    
    Type IQueryable.ElementType
    {
        get { return typeof(T); }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return this.provider; }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)this.provider.Execute(this.expression)).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)this.provider.Execute(this.expression)).GetEnumerator();
    }

    public override string ToString()
    {
        return this.provider.GetQueryText(this.expression);
    }
}
```

[ポイント1]
`IQueryable`を実装するには下記メンバーを用意する必要がある。

```c#
Expression IQueryable.Expression
Type IQueryable.ElementType
IQueryProvider IQueryable.Provider
```

[ポイント2] 
`IQueryable`は`IEnumerable`を継承するのでこのクラスも`GetEnumerator`を実装する必要がある。実装上は`IQueryProvider`が持つ`Execute`メソッドを呼ぶことでイテレータが返る。

本記事の煩雑を避けるため、`IQueryProvider`を実装したコードの記載は割愛します。
対象コードのリンクは以下になります。
=> [QueryProvider](https://github.com/momotaro98/csharp-sandbox/blob/master/IQueryableBehaviours/QueryProvider.cs)
=> [MyQueryProvider](https://github.com/momotaro98/csharp-sandbox/blob/master/IQueryableBehaviours/MyQueryProvider.cs)

`IQueryable`は与えたれたLinq式から式木(`IQueryable.Expression`)を用意し、その式木を使って`IQueryable.Provider`がデータソースに対するクエリを生成し発行します。

## Linqメソッド実行時の動作を比較

以下のコードを使って上記で作成した`IEnumerable<T>`と`IQueryable<T>`の実装クラスを比較してみます。

```c#:比較用コード
static class Program
{
    static void Main()
    {
        Console.WriteLine($"========== Start {nameof(UseMyEnumerable)} ==========");
        UseMyEnumerable();
        Console.WriteLine($"========== Start {nameof(UseMyQueryProvider)} ==========");
        UseMyQueryProvider();
    }
    
    private static void UseMyEnumerable()
    {
        var e = new MyEnumerable();
        var q2 = e.Where(x => x % 2 == 0);
        var q3 = q2.Select(x => x * x);
        foreach (var val in q3)
        {
            Console.WriteLine($"{val}");
        }
    }

    private static void UseMyQueryProvider()
    {
        var q1 = MyQueryProvider.CreateQueryable<int>();
        Console.WriteLine($"q1: {q1.Expression}");

        var q2 = q1.Where(x => x % 2 == 0);
        Console.WriteLine($"q2: {q2.Expression}");

        var q3 = q2.Select(x => x * x);
        Console.WriteLine($"q3: {q3.Expression}");
        
        // Executeメソッドがnullのみを返すのでこのforeachで"ランタイム"エラーとなる。
        // foreachのコメントアウトを外してもコンパイルは通る。
        //foreach (var val in q3) 
        //{
        //   Console.WriteLine($"{val}");
        //}
    }
}
```

### 実行結果

```
========== Start UseMyEnumerable ==========
16
64
0
36
4
========== Start UseMyQueryProvider ==========
q1:
q2: .Where(x => ((x % 2) == 0))
q3: .Where(x => ((x % 2) == 0)).Select(x => (x * x))
```

`IQueryable<T>`はLinqメソッドが呼ばれるたびに式木(Expression)が更新されています。コード上のコメントにもあるように、本記事での`IQueryable<T>`実装クラスの`IQueryProvider`の`Execute`メソッドはnullしか返さないのでforeach時にはNullReferenceのランタイムエラーが発生します。

### `IEnumerable<T>`実装クラスの内部をみてみる

上記の比較用コードをデバッグして内部を見てみます。

![IEnumerable_Debugging.png](https://qiita-image-store.s3.amazonaws.com/0/91562/f603d22a-81a0-df34-3896-4ecd3a524769.png)

こちらのスクショの赤枠からわかるように、`IEnumerable<T>`では`Where`のLinqメソッド実行時には実態のコレクションがインメモリ上に展開されます。
`IEnumerable<T>`は処理自体は遅延実行であり利用者側のメモリには直ちに評価され展開されませんが、元のコレクションは内部的にはLinqメソッド実行時には保持されます。

### `IQueryable<T>`実装クラスの内部をみてみる

![IQueryable_Debugging.png](https://qiita-image-store.s3.amazonaws.com/0/91562/1dcc17de-6a29-933f-f3af-65d834c2e6e1.png)

`IQueryable<T>`はLinqメソッド実行時はあくまでクエリが構築されるだけのようです。
`foreach`などの評価で初めてクエリが外部ソースに発行され結果が取得されるので、`IQueryable<T>`は遅延"読み込み"(lazy loading)の特徴があると説明されます。

# .NETのソースをみてみる

[`Enumerable`のWhereに対応するメソッドのソースコード](https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,32)からわかるように、インメモリ上に自身が持つ反復子を取り出しています。

一方で[`IQueryable`の`Where`メソッドのソースコード](https://referencesource.microsoft.com/#System.Core/System/Linq/IQueryable.cs,90)を見る限り、`IQeuryProvider.CreateQuery`を使って式木を更新しているだけのようです。

# おわりに

`IQueryable<T>`は外部リソースを扱うLinqプロバイダのために提供されているもので、`IEnumerable<T>`と`IQueryable<T>`は、クエリ発行がプログラムのインメモリ上か外部リソースのプログラム上かという点で異なっていました。

そして、.NETの実装としてその違いは、Where句などのLinqメソッドで確認することができました。

# 参考

* http://neue.cc/2011/01/13_295.html
* https://ufcpp.net/study/csharp/sp3_iqueryable.html
* https://blogs.msdn.microsoft.com/mattwar/2007/07/30/linq-building-an-iqueryable-provider-part-i/
* https://qiita.com/kenji-yokoi/items/f1bf511b6894a8d33480
* https://referencesource.microsoft.com