1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINQのWhereは何を保存しているのか:遅延実行と参照のコピーを内部実装から追う

1
Posted at

はじめに

遅延実行と参照のコピーの辺りでしっかりと把握せずに使用していた部分があったので一度言語化してみようかなと思います。
値型と参照型のような難しさはあったが、一度理解すると新しいインスタンスが代入された時にどうなるかなど、深い理解を得られました。

遅延実行

コード

var list = new List<int> { 1, 2, 3 };
var query = list.Where(x => x > 1);
list.Add(4);
Console.WriteLine(query.Count());

問
出力結果はどうなるか?

回答
3が出力される

LINQが実行されるのは値を取得するとき

上記のコードを実行した結果は、query変数に代入しているタイミングだと考えていましたが、実際はConsole.WriteLineの部分で、Countメソッドを実行しているタイミングでした。

参照関係

コード

var list2 = new List<int> { 1, 2, 3 };
var query2 = list2.Where(x => x > 1);
list2 = new List<int> { 10, 20, 30, 40 };
Console.WriteLine(query2.Count());

問
出力結果はどうなるか?

回答
2が出力される

queryに保存される参照を具体的に理解出来ていなかった

このコードの出力予想も間違え、当初は4が出力されると考えていました。

理由としては、list2に格納されるインスタンスへの参照がnewされたものに切り替わるだけなので、queryを実行したときには、新しいリストの中身で計算されると考えたためです。

しかし、Whereメソッドの中身を追っていくとこれが間違いであることが分かります。


Whereメソッド->ListWhereIteratorクラスのコンストラクタまでは以下のように繋がっています。
(var queryの部分では、IEnumerableが返ってきているため、このListWhereIteratorが返っています。)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    // nullチェック(省略)

    if (source is List<TSource> list)
    {
        return new ListWhereIterator<TSource>(list, predicate);
    }
    return new IEnumerableWhereIterator<TSource>(source, predicate);
}
private sealed partial class ListWhereIterator<TSource> : Iterator<TSource>
{
    private readonly List<TSource> _source;
    private readonly Func<TSource, bool> _predicate;

    public ListWhereIterator(List<TSource> source, Func<TSource, bool> predicate)
    {
        _source = source;        // 参照をフィールドに保存するだけ
        _predicate = predicate;
    }
}

コードからわかるように、最終的に_sourceに元のlist変数がさしているインスタンスへの参照が格納されます。
この時点で、インスタンスへの参照が別途ListWhereIteratorのプライベート変数に格納されるため、保存されるのは参照の経路ではなく、実データへの参照だと再認識しました。

そのため、元のlist変数の保持しているインスタンスの参照が変わろうとquery変数に格納されているIEnumerableが保持しているListの参照は変わりません。

メモリの状態について、以下図解で補足です。

Whereを呼んだ直後
  list  ──→ A [1,2,3]
  query ──→ Q
            Q._source ──→ A

list.Add(4) の後
  list  ──→ A [1,2,3,4]
            Q._source ──→ A   (同じAなので見える)

list = new ... の後
  list  ──→ B [10,20,30,40]
            Q._source ──→ A [1,2,3]   (矢印はAのまま)
1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?