I have explained this confusion in a blog at https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16. I will try to summarize it here so that you can have a clear idea.
Reference means, "Need":
First of all, you need to understand that, if object A holds a reference to object B, then, it will mean, object A needs object B to function, right? So, the garbage collector won't collect object B as long as object A is alive in the memory.
+= Means, injecting reference of Right side object to the left object:
The confusion comes from the C# += operator. This operator does not clearly tell the developer that, the right-hand side of this operator is actually injecting a reference to the left-hand side object.
And by doing so, object A thinks, it needs object B, even though, from your perspective, object A should not care if object B lives or not. As object A thinks object B is needed, object A protects object B from the garbage collector as long as object A is alive. But, if you did not want that protection given to the event subscriber object, then, you can say, a memory leak occurred. To emphasize this statement, let me clarify that, in the .NET world, there is no concept of memory leak like a typical C++ unmanaged program. But, as I said, object A protects object B from garbage collection and if that was not your intention, then you can say a memory leak happened because object B was not supposed to be living in the memory.
You can avoid such a leak by detaching the event handler.
How to make a decision?
There are lots of events and event handlers in your whole code-base. Does it mean, you need to keep detaching event handlers everywhere? The answer is No. If you had to do so, your codebase will be really ugly with verbose.
You can rather follow a simple flow chart to determine if a detaching event handler is necessary or not.
Most of the time, you may find the event subscriber object is as important as the event publisher object and both are supposed to be living at the same time.
Example of a scenario where you do not need to worry
For example, a button click event of a window.
Here, the event publisher is the Button, and the event subscriber is the MainWindow. Applying that flow chart, ask a question, does the Main Window (event subscriber) supposed to be dead before the Button (event publisher)? Obviously No. Right? That won't even make sense. Then, why worry about detaching the click event handler?
An example when an event handler detachment is a MUST.
I will provide one example where the subscriber object is supposed to be dead before the publisher object. Say, your MainWindow publishes an event named "SomethingHappened" and you show a child window from the main window by a button click. The child window subscribes to that event of the main window.
And, the child window subscribes to an event of the Main Window.
From this code, we can clearly understand that there is a button in the Main Window. Clicking that button shows a Child Window. The child window listens to an event from the main window. After doing something, the user closes the child window.
Now, according to the flow chart I provided if you ask a question "Does the child window (event subscriber) supposed to be dead before the event publisher (main window)? The answer should be YES. Right? So, detach the event handler. I usually do that from the Unloaded event of the Window.
A rule of thumb: If your view (i.e. WPF, WinForm, UWP, Xamarin Form, etc.) subscribes to an event of a ViewModel, always remember to detach the event handler. Because a ViewModel usually lives longer than a view. So, if the ViewModel is not destroyed, any view that subscribed event of that ViewModel will stay in memory, which is not good.
Proof of the concept using a memory profiler.
It won't be much fun if we cannot validate the concept with a memory profiler. I have used JetBrain dotMemory profiler in this experiment.
First, I have run the MainWindow, which shows up like this:
Then, I took a memory snapshot. Then I clicked the button 3 times. Three child windows showed up. I have closed all of those child windows and clicked the Force GC button in the dotMemory profiler to ensure that the Garbage Collector is called. Then, I took another memory snapshot and compared it. Behold! our fear was true. The Child Window was not collected by the Garbage collector even after they were closed. Not only that but the leaked object count for the ChildWindow object is also shown as "3" (I clicked the button 3 times to show 3 child windows).
Ok, then, I detached the event handler as shown below.
Then, I have performed the same steps and checked the memory profiler. This time, wow! no more memory leak.