Нужно ли настраивать определенные события при удалении объекта?

Допустим, у нас есть 2 объекта, Broadcaster и Listener. Broadcaster имеет событие под названием Broadcast, на которое подписчик подписчика. Если прослушиватель размещен без отмены подписки из события Broadcast, он будет сохранен в памяти из-за того, что делегат события ссылается на него, который содержит Broadcaster.

Что мне интересно, если Broadcaster удален без отмены подписчика или настройки Broadcaster Broadcast = null, будет ли храниться в памяти?

Я не смог найти что-либо с жестким ответом на этот вопрос, кроме одного блоггера, который считает, что не устанавливать событие в null будет держать источник в памяти (найдено здесь).

Я хотел бы услышать объяснение, почему или почему нет.

Спасибо.

UPDATE: Тема форума, где один разработчик указывает, что события должны быть установлены в нуль, но Jon Skeet указывает, что это необязательно, но не уточняет.

Ответ 1

Обратите внимание, что делегаты не сохраняют живого издателя (они только сохраняют цель = абонент живым), поэтому никакое количество подписей (по отдельности) не будет поддерживать вещание. Таким образом, с этой точки зрения не имеет значения, выбрано или нет. Когда нет элементов, ссылающихся на вещатель (и для подписки на события не имеет значения для этого), он будет иметь право на сбор.

По сути, делегат является (списком) парой (-ей) ссылок MethodInfo и object; метод вызова и объект для вызова как "arg0" (aka this). Он просто не имеет ссылки на объект, поднимающий событие.

Здесь доказательство того, что слушатель не не поддерживает источник; вы должны увидеть, что "Источник 1" собирается, хотя у нас все еще есть подписчик, который подписывается. Как и ожидалось, "Listener 2" не собирается, поскольку у нас все еще есть соответствующая вещательная компания:

class DataSource
{
    public DataSource(string name) { this.name = name; }
    private readonly string name;
    ~DataSource() { Console.WriteLine("Collected: " + name); }

    public event EventHandler SomeEvent;
}
class DataListener
{
    public DataListener(string name) { this.name = name; }
    private readonly string name;
    ~DataListener() { Console.WriteLine("Collected: " + name); }
    public void Subscribe(DataSource source)
    {
        source.SomeEvent += SomeMethodOnThisObject;
    }
    private void SomeMethodOnThisObject(object sender, EventArgs args) { }
}

static class Program
{
    static void Main()
    {
        DataSource source1 = new DataSource("Source 1"),
                source2 = new DataSource("Source 2");
        DataListener listener1 = new DataListener("Listener 1"),
                listener2 = new DataListener("Listener 2");
        listener1.Subscribe(source1);
        listener2.Subscribe(source2);
        // now we'll release one source and one listener, and force a collect
        source1 = null;
        listener2 = null;
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers(); // source 1 gets collected, ONLY

        Console.WriteLine("Done");
        Console.ReadLine();
        GC.KeepAlive(source2); // prevents collection due to optimisation
        GC.KeepAlive(listener1); // prevents collection due to optimisation
    }
}

Ответ 2

Нет. Цель делегата в событии Broadcast ссылается на объект Listener. Это сохранит объект Listener. Объект Listener не имеет ссылки на объект Broadcast.

Следите за терминологией. Устранение объекта Broadcast ничего не делает. Это должен быть сбор мусора, который может произойти только тогда, когда на объект нет ссылок. Когда это произойдет, объект делегата будет автоматически собран, так как единственная ссылка на него - это внутренний список целей делегата, поддерживаемый объектом делегирования частного события. Это также удаляет ссылку, которую делегат имеет для слушателя. Если нет других ссылок на слушателя, они также будут собраны. Если он все еще есть, он больше не будет получать уведомления о событиях. Короче говоря: вам не нужно явно устанавливать событие в null в классе Broadcast.

Не совсем то же самое в слушателе, на него ссылается событие, на которое оно подписано. Если он объявлен непригодным для бизнеса (размещен), но вещатель по-прежнему жив, он должен явно удалить свою подписку на события. Класс SystemEvents является экстремальной версией этого, его события статичны. Увольнение на делегате, которое ссылается на находящегося слушателя, - это то, что вы обычно замечаете.

Большинство практических объектных моделей пытаются гарантировать, что объекты-слушатели исчезают при выходе родителя. Windows Forms будет хорошим примером. Нет необходимости явно отменить подписку на события.