Подтвердить что ты не робот

Почему компилятор С# генерирует один класс для захвата переменных нескольких лямбда?

Предположим, что мы имеем такой код:

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);
    }
}

Здесь action1 и action2 имеют полностью разделенный набор захваченных переменных - rCopy был создан специально для этого. Тем не менее, компилятор генерирует только один класс для захвата всего (проверенный сгенерированный IL). Я полагаю, что это сделано по соображениям оптимизации, но это позволяет очень трудно выявлять ошибки утечки памяти: если a и b записаны в одном классе, GC не может собрать как по крайней мере до тех пор, пока любой из lambdas ссылка.

Есть ли способ убедить компилятор создать два разных класса захвата? Или любая причина, по которой это невозможно сделать?

P.S. Несколько более подробно, в моем блоге: здесь и здесь.

4b9b3361

Ответ 1

Вы вновь обнаружили известный недостаток в реализации анонимных функций в С#. Я описал проблему в своем блоге в 2007 году.

Есть ли способ убедить компилятор создать два разных класса захвата?

Нет.

Или любая причина, по которой это невозможно сделать?

Нет никакой теоретической причины, по которой невозможно было бы разработать улучшенный алгоритм для разделения закрытых переменных, чтобы они были подняты в разные классы замыкания. Мы не сделали этого по практическим соображениям: алгоритм сложный, дорогостоящий, чтобы стать правильным и дорогим для тестирования, и у нас всегда были более высокие приоритеты. Надеюсь, это изменится в Рослине, но мы не гарантируем.

Ответ 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));
}