Я объяснил эту путаницу в блоге по адресу https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16 . Я постараюсь обобщить это здесь, чтобы вы могли иметь четкое представление.
Справочное обозначение «Потребность»:
Прежде всего, вы должны понимать, что если объект A содержит ссылку на объект B, то это будет означать, что объект A нуждается в объекте B, чтобы функционировать, верно? Таким образом, сборщик мусора не будет собирать объект B, пока объект A находится в памяти.
Я думаю, что эта часть должна быть очевидной для разработчика.
+ = Означает, что вставка ссылки правого объекта на левый объект:
Но путаница возникает из-за оператора C # + =. Этот оператор четко не сообщает разработчику, что правая часть этого оператора фактически вводит ссылку на левый объект.
Таким образом, объект A думает, что ему нужен объект B, хотя, с вашей точки зрения, объект A не должен заботиться о том, живет ли объект B или нет. Поскольку объект A считает, что объект B необходим, объект A защищает объект B от сборщика мусора, пока объект A жив. Но если вы не хотите, чтобы эта защита предоставлялась объекту подписчика на событие, то вы можете сказать, что произошла утечка памяти.
Вы можете избежать такой утечки, отсоединив обработчик событий.
Как принять решение?
Но во всей вашей кодовой базе есть много событий и обработчиков событий. Означает ли это, что вам нужно постоянно отсоединять обработчики событий? Ответ - нет. Если бы вам пришлось это сделать, ваша кодовая база будет очень уродливой с многословной.
Вы можете использовать простую блок-схему, чтобы определить, требуется ли отсоединение обработчика событий.
В большинстве случаев вы можете обнаружить, что объект подписчика на событие так же важен, как и объект публикации события, и предполагается, что оба они живут одновременно.
Пример сценария, в котором вам не нужно беспокоиться
Например, событие нажатия кнопки окна.
Здесь издателем события является кнопка, а подписчиком события - главное окно. Применяя эту блок-схему, задайте вопрос, должно ли основное окно (подписчик события) быть мертвым перед кнопкой (издателем события)? Очевидно, нет. Верно? Это даже не имеет смысла. Тогда зачем беспокоиться об отключении обработчика события click?
Пример, когда отсоединение обработчика события является ОБЯЗАТЕЛЬНЫМ.
Я приведу один пример, когда объект подписчика должен быть мертвым перед объектом издателя. Скажем, ваше MainWindow публикует событие с именем SomethingHappened, и вы показываете дочернее окно из главного окна нажатием кнопки. Дочернее окно подписывается на это событие главного окна.
И дочернее окно подписывается на событие Главного окна.
Из этого кода мы можем ясно понять, что в главном окне есть кнопка. Нажав на эту кнопку, вы увидите дочернее окно. Дочернее окно слушает событие из главного окна. Сделав что-то, пользователь закрывает дочернее окно.
Теперь, в соответствии с блок-схемой, которую я предоставил, если вы зададите вопрос «Предполагается, что дочернее окно (подписчик события) должно быть мертвым до публикации события (главное окно)? Ответ должен быть ДА. Верно? Итак, отсоедините обработчик события». Я обычно делаю это из события Unloaded окна.
Практическое правило. Если ваше представление (т. Е. WPF, WinForm, UWP, форма Xamarin и т. Д.) Подписывается на событие ViewModel, всегда не забывайте отключать обработчик события. Потому что ViewModel обычно живет дольше, чем представление. Таким образом, если ViewModel не уничтожен, любое представление, подписанное на событие этой ViewModel, останется в памяти, что не очень хорошо.
Доказательство концепции с использованием профилировщика памяти.
Будет не очень весело, если мы не сможем проверить концепцию с помощью профилировщика памяти. В этом эксперименте я использовал профилировщик JetBrain dotMemory.
Сначала я запустил MainWindow, которое выглядит так:
Затем я сделал снимок памяти. Затем я нажал кнопку 3 раза . Появились три детских окна. Я закрыл все эти дочерние окна и нажал кнопку Force GC в профилировщике dotMemory, чтобы убедиться, что вызывается сборщик мусора. Затем я сделал еще один снимок памяти и сравнил его. Вот! наш страх был правдой. Детское окно не было собрано сборщиком мусора даже после его закрытия. Не только это, но и количество просочившихся объектов для объекта ChildWindow также отображается как « 3 » (я нажал кнопку 3 раза, чтобы показать 3 дочерних окна).
Хорошо, тогда я отключил обработчик событий, как показано ниже.
Затем я выполнил те же действия и проверил профилировщик памяти. На этот раз вау! нет больше утечки памяти.