40

そのようなコードがあると仮定します:

public class Observer
{
    public event EventHandler X = delegate { };
}

public class Receiver
{
    public void Method(object o) {}
}

public class Program
{
    public static void DoSomething(object a, object b, Observer observer, Receiver r)
    {
        var rCopy = r;
        EventHandler action1 = (s, e) => rCopy.Method(a);
        EventHandler action2 = (s, e) => r.Method(b);
        observer.X += action1;
        observer.X += action2;
    }

    public static void Main(string[] args)
    {
        var observer = new Observer();
        var receiver = new Receiver();
        DoSomething(new object(), new object(), observer, receiver);
    }
}

ここでaction1action2キャプチャされた変数のセットを完全に分離しました-rCopyこれのために特別に作成されました。それでも、コンパイラはすべてをキャプチャするために1つのクラスのみを生成します(生成されたILをチェック)。最適化の理由で行われていると思いますが、メモリリークのバグを見つけるのが非常に困難です。単一のクラスでキャプチャされた場合a、少なくともラムダのいずれかが参照bされている限り、GCは両方を収集できません。

コンパイラに2つの異なるキャプチャクラスを生成するように説得する方法はありますか?またはそれができない理由は何ですか?

PS私のブログで、もう少し詳しく説明します:ここここ

4

2 に答える 2

35

C#での無名関数の実装における既知の欠点を再発見しました。私は2007年に私のブログで問題を説明しました。

コンパイラに2つの異なるキャプチャクラスを生成するように説得する方法はありますか?

いいえ。

またはそれができない理由は何ですか?

クローズドオーバー変数を分割して、それらが異なるクロージャクラスに引き上げられるようにするための改善されたアルゴリズムを考案できなかった理論的な理由はありません。実用的な理由でそうしていません。アルゴリズムは複雑で、正しく実行するには費用がかかり、テストには費用がかかります。また、常に優先順位が高くなっています。うまくいけば、それはRoslynで変わるでしょうが、私たちは保証をしていません。

于 2012-08-17T18:01:51.430 に答える
27

コンパイラのコード書き換えロジックに実際的な制限があることは間違いありません。簡単なことではありません。回避策は非常に簡単です。ラムダを別のメソッドで作成して、非表示のクラスの2つの別々のインスタンスを取得します。

public static void DoSomething(object a, object b, Observer observer, Receiver r) {
    var rCopy = r;
    observer.X += register(r, a);
    observer.X += register(rCopy, b);
}
private static EventHandler register(Receiver r, object obj) {
    return new EventHandler((s, e) => r.Method(obj));
}
于 2012-08-17T16:50:19.687 に答える