ネコのために鐘は鳴る

寺院に住み着くパソコ〇好き

(C#) ラムダ式による this のキャプチャ

ラムダ式による外部変数のキャプチャは、コンパイラによって暗黙的に匿名クラスが作られ、コールされるたびに匿名クラスのインスタンスnewされるため、ヒープアロケーションが発生する。

using System;
public class Class
{    
    public Action Method(int num)
    {
        return () => Console.WriteLine(num);
    }
}

上記のコードはコンパイラによって以下のコードと同義に解釈される。(コンパイラ生成コードの名前は見やすさの為に適当に変えてある)

public class Class
{
    [CompilerGenerated]
    private sealed class HiddenClass
    {
        public int num;
        internal void A() => Console.WriteLine(num);
    }

    public Action Method(int num)
    {
        var tmp = new HiddenClass();
        tmp.num = num;
        return tmp.A;
    }
}

外部変数のキャプチャは、暗黙的に、キャプチャされた変数をメンバに持つクラスインスタンスを生成し、そのインスタンスメソッドがデリゲートとなる。無駄なガベージを生むため、パフォーマンス優先の場所では可能ならキャプチャは避けるのが良い。

 

そして本題。キャプチャは必ず暗黙的クラスインスタンスを生むのかというと、実はそうならない場合もある。その例は以下の場合。

using System;
public class Class
{
    private int _num;    
    public Action Method()
    {
        return () => Console.WriteLine(_num);
    }
}

先ほどと異なるのは、クラスのインスタンスメンバをキャプチャしている点。この場合、キャプチャされているのはthisという考え方になる。このコードのコンパイラによる解釈は以下のようになる。

using System;
public class Class
{
    private int _num;

    public Action Method()
    {
        return A;
    }

    [CompilerGenerated]
    private void A() => Console.WriteLine(_num);
}

thisのみをキャプチャしているラムダ式は、自身のクラスインスタンスメソッドに置き換わるため、無駄なアロケーションが発生しない。よかったですねぇ~