Я пояснив цю плутанину в своєму блозі за адресою 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 живий. Але, якщо ви не хотіли, щоб захист, наданий об’єкту абонента події, тоді, можна сказати, сталася витік пам'яті.
Ви можете уникнути такого витоку, від'єднавши обробника події.
Як прийняти рішення?
Але у всій вашій кодовій базі є маса подій та обробників подій. Це означає, що вам потрібно тримати відокремлення обробників подій скрізь? Відповідь - Ні. Якщо вам довелося це зробити, ваша кодова база буде дійсно некрасивою з багатослівними.
Ви можете скористатися простою схемою потоку, щоб визначити, потрібен обробник подій, що відривається, чи ні.
Більшу частину часу ви можете виявити, що об’єкт, який підписався на подію, є таким же важливим, як об’єкт видавця події, і обидва, як передбачається, живуть одночасно.
Приклад сценарію, коли вам не потрібно хвилюватися
Наприклад, подія натискання кнопки вікна.
Тут видавцем події є кнопка, а підписником події є MainWindow. Застосовуючи цю схему потоку, задайте питання, чи не повинно Головне вікно (абонент події) бути мертвим перед кнопкою (видавець подій)? Очевидно, ні. Це навіть не має сенсу. Тоді навіщо турбуватися про від'єднання обробника подій клацання?
Приклад, коли загін обробника подій ОБОВ'ЯЗКОВО.
Я наведу один приклад, коли передплатний об’єкт повинен бути мертвим перед видавчим об’єктом. Скажімо, ваше головне вікно публікує подію під назвою "SomethingHappened", і ви показуєте дочірнє вікно з головного вікна натисканням кнопки. Дочірнє вікно підписується на цю подію головного вікна.
І дочірнє вікно підписується на подію Головного вікна.
З цього коду ми можемо чітко зрозуміти, що в головному вікні є кнопка. При натисканні на цю кнопку відображається дочірнє вікно. Дочірнє вікно слухає подію з головного вікна. Зробивши щось, користувач закриває вікно дитини.
Тепер, згідно з графіком поданих мною таблиць, який я надав, якщо ви ставите запитання "Чи повинно дочірнє вікно (абонент події) бути мертвим перед видавцем події (головне вікно)? Відповідь має бути ТАК. Так? Так, від'єднайте обробник події Я зазвичай роблю це з події Unloaded Window.
Правило: Якщо ваш погляд (наприклад, WPF, WinForm, UWP, форма Xamarin тощо) передплачує подію ViewModel, завжди пам’ятайте, що потрібно від'єднати обробник подій. Тому що ViewModel зазвичай живе довше, ніж представлення. Отже, якщо ViewModel не буде знищений, будь-який погляд, який підписався на подію ViewModel, залишиться в пам'яті, що не добре.
Доведення концепції за допомогою профайлера пам'яті.
Це буде не дуже весело, якщо ми не зможемо затвердити концепцію з профілером пам'яті. У цьому експерименті я використав JetBrain dotMemory профілер.
По-перше, я запустив MainWindow, яке відображається так:
Потім я зробив знімок пам’яті. Тоді я натискав кнопку 3 рази . З’явилися три дитячі вікна. Я закрив усі ці дочірні вікна і натиснув кнопку Force GC у профілі dotMemory, щоб переконатися, що викликається збирач сміття. Потім я зробив ще один знімок пам'яті і порівняв його. Ось! наш страх був правдивим. Дитяче вікно збирач сміття не збирався навіть після їх закриття. Не тільки це, але і кількість протікаючих об'єктів для об'єкта ChildWindow також показана " 3 " (я натиснув кнопку 3 рази, щоб показати 3 дочірні вікна).
Добре, тоді я відключив обробник подій, як показано нижче.
Потім я виконав ті ж дії і перевірив профайлер пам'яті. Цього разу, вау! більше немає витоку пам'яті.