There is a lot of fear among many new developers about C# memory leak caused by event handlers. At some places, the idea of memory leak became so scary that some developers get paranoid about any event handler code they see like this one.
btnSayHello.Click += btnSayHello_Click;
But, do you need to be feared about it? And do you always need to detach the event handler like:
btnSayHello.Click -= btnSayHello_Click;
The answer is, No, I mean, not always. But, please note. I did not say 'Never.' I said, 'Not always.' That means, yes, you should also know when you need to worry about event handlers and associated memory leaks. Okay, how to determine when not to worry and when to worry?
That's the place things get confusing for many new developers. So, I thought, Enough is Enough. We need to come up with some clear understanding of this fuss and develop an easy rule of thumbs that we can always remember when coding. The basic idea is straightforward and easy to understand. The confusion comes from a C# += syntax of the event handler, and that's what I will explain.
Say, someone has come to your room to clean. He is picking up all unnecessary items and throwing in the garbage.
How will he determine if something is garbage or not? Say, he found a piece of paper on the ground. He must ask you if you need that paper, right? If you do not need that, then he can throw that to the garbage bin. The paper is found on the carpet. The paper needs the carpet to stay on. You need the carpet for sure. Does it mean, the paper cannot be thrown to the garbage bin because the paper needs the carpet and carpet is not garbage? No. that will be an absurd logic, right?
So, what have we learned? We learned that it does not matter if an unnecessary thing needs anything important when determining if the thing should be thrown into the garbage bin. What matters is that, if the thing itself is needed by something important.
The same example can be applied to the .NET Garbage collector. Think about two classes, A and B, where A keeps a reference of B, as shown here.
Here we see that the object of class A holds a reference of an object of class B. That means, the object of A needs object of B. Now, the question is, Is the object of A important (or needs to stay alive)?, If the answer is yes, then, Garbage collector will keep the object of A in memory. As the object of B is needed by the object A, so, the object of B will also be kept in the memory. Think about the analogy of carpet, paper, and you. Say, here, You represent an object of class A., And the piece of paper represents an object of classes B. As you need the piece of paper, the paper will not be thrown to the garbage bin. Not the other way around as the paper needs the carpet and carpet is important, so keep the paper on the carpet, right?
Let's get back to our class example. So far, so good. Say, now, class A does not need anything from class B. So, there is no reference of class B in class A. According to our analogy of carpet, paper, and you, where you represent the object of A, the paper represents the object of B, this time; you are not claiming that the paper is important to you. So the garbage collector should be able to pick up that paper and throw it to the garbage bin. Right?
This time, class A is featured with an event named "SomethingHappened."
And class B subscribes to that event, as shown here.
Now, this time, as class B holds a reference to class A, looks like class B needs class A. But, who cares, if class B needs something, if the object of class B is needed by anyone, right? Same as before, the garbage collector found that the object of class A is still important to keep in memory, okay fine. Then it comes to check the object of class B. This time, the garbage collector comes and sees that the object of class B is not needed by anyone. So. it is expected to be collected to the garbage bin, right?
Well, surprise! The Garbage collector cannot collect the object of B.
Now, is the sweating time...uhh... why? I mean, why not? Why doesn't; the Garbage Collector collects the object of B?
When a situation arises like that, where you expect something to be gone from the memory, but .NET GC does not think that it should be gone from the memory, then we call this situation as a memory leak in .NET.
Now, say that your application has finished all necessary works with an object of B and now. So, what do you expect? The object of B should be collected by GC, right? GC finds that object B is listening to an event of class A. Then, it checks if the object of A referenced by class B is live in memory. If so, then, it cannot collect the object B. So, in .NET terms, a memory leak occurs.
But, as a developer, what have you done wrong so that the .NET Garbage Collector cannot collect the object of B even though no other objects needed the object b?
Here, the object of B subscribes to an event of class A. That means, if something happens to the object of A, the object of B must be notified, that is the idea, right? But, according to science, there is no magic. How can possibly object of A notify object of B if the object of A does not hold any reference of the object of B? In order to make that notification possible, the .NET framework (or Core, whatever the framework you use for C#) creates a hidden reference of object B and handover to the object of A so that object of A can notify to object of B if something happens.
So, the C# syntax += means, that, the right side thing injects a reference of itself to the left side thing.
Now, things are getting interesting. Remember again, if a living object holds a reference to another object, it will mean, the living object NEEDS the other object. In other words, if object A holds a reference of object B, then, it will mean, object A needs object B. So, object A will try to protect the object B.
Therefore, the other object cannot be collected from memory. From the developer's perspective, the object of B should have been collected by the garbage collector when the task given to the object B is completed, but the .NET Garbage collector does not collect the object of B, because the object of A is protecting the object of B in this way.
From the theory of Evolution, many of us might be familiar with the famous statement "Survival of the fittest." Here, in the .NET world, we see "Survival of the Event Listener.". Who listens, he/she survives.
So, how can you make sure that the object of A does not protect the object of B once the task given to the object of B is completed?
Answer: Detach the event handler of the event from the object of A, after all, the task is completed by the object of B. It can be a dispose method or Unloaded event handler of a Window or immediately within the same method that uses the object.
When you detach the event handler, the .NET tells the object of A that the object of B is not listening to it anymore. So, the reference of the object of B is removed from the object of A. And therefore, the Garbage Collector sees that the living object of A does not need the object of B anymore. So, it collects the object of B.
In other words, when do I need to make sure that the event handler detachment is done? Say, object A and object B, both are expected to be living in the memory at the same time or almost the same duration. For example, Window control in WPF creates a Button control. The Button's Click event is subscribed by the window.
Now, subscribing to the Click event of the button, the button got a hidden reference of the window behind the scene so that the button can notify the window. That means the button NEEDS the object of the window. So, if the button is alive, the window cannot be collected. But, a button created inside the window, how can it be alive anyway without the window being alive? The button is created inside the window. So, as long as the window is alive, you will need the button to be alive as well. When the window is closed (destroyed), the button will be destroyed along with the window. Would you ever have a situation when that particular button will be useful in your application, but the containing window is expected to be destroyed? That does not even make sense. So, the answer is NO. IF the answer is No, then why should you worry about detaching this Click event handler of that button? You do not need to worry about it.
In WPF or UWP or Xamarin Form, typically, a button is created in XAML, and the Click event handler is created from the XAML, as shown below.
Now, you understand that we do not need to worry about such event handlers.
Say, you show a child Window from your main window. And if something happens to your child window, you want to notify the main window. Say, the user checked a checkbox or clicked a button in the child window; you want to update status in your main window accordingly.
And your main window creates the child window, subscribers to the event of the child window, and show the window.
Here, the child window receives a hidden reference of the main window so that the child window can notify the main window about that event. So, the child Window gets a NEED for the Main Window. Thus, the Garbage Collector cannot collect the main window if the child window is active. Now, think about it. The Main Window is the parent. Will you ever worry if the main window cannot be collected by the Garbage Collector when the child window is active? You do not even want to get your Main Window collected when your child window is active. Right? So, you do not need to worry about detaching the event handler in this case.
Consider the same child window in your main window. This time, your child window needs to be notified of some events from your main window. For example, say the user clicks a button in the main window, you want to show some information because of that button click, in the child window. So, the child window subscribes to an event of your main window.
In the ChildWindow class, you subscribe to the event.
Now, when the child window listens to the event of the main window, the main window receives a reference for Child Window. When you close the child window, you expect that the object of ChildWindow should be cleared from the memory, right? But, that does not happen seamlessly if you listen to an event of the parent window from the child window. By subscribing to an event of the Parent window, the parent window receives a hidden reference of the child window. As long as the Main Window is living in the memory, the main window will keep the child window live in the memory, even after the child window is closed.
If you show the child window, say 50 times, you will have 50 instances of the ChildWindow object living in your memory, and that can be a big problem. In fact, you can hit the MemoryOverflowException.
You can detach the event handler at the Unloaded event of the ChildWindow. Like this:
In order to prove the idea, I have performed memory profiling using the Jet Brain Dot Memory profiler. I ran the Main Window, which shows up like this.
Then, I have taken a snapshot of the memory. I clicked the "Say Hello" button 3 times, where the click event handler of this button shows 3 Child Window. Then, I have closed all those 3 child windows and flicked the Force GC button to force the garbage collector to execute all cleanup.
Then, I have taken another snapshot of the memory. I have compared the 2 snapshots and discovered that my idea was correct. As I have clicked the button 3 times, 3 ChildWindow was created, and even after closing those child windows, and even after force running GC, 3 objects of ChildWindow was still living in the memory, as shown in the following screenshot taken from the JetBrains dotMemory profiler.
Not only that, but the dotMemory profiler also gives a nice analysis view of a snapshot where an event handler leak section is especially demonstrated. In that section, we can clearly see that, because of the 3 button clicks, the 3 Child Window which was never collected, caused a memory leak.
After this analysis, I have detached the event handler in the Unloaded event of the child window.
Then performed the same steps. Ran the Main Window and clicked the button 3 times and taken memory snapshot. Then compared the snapshot. Wow, I did not see the leaking 3 ChildWindow objects like the last time. The Event Handlers leak section of the Snapshot analysis window shows no ChildWindow object like before.
I never had any need to use a static event. I cannot judge if someone finds a static event useful. But, if you ever have to subscribe to a static event like this:
Then, be wary!! A static event will make your subscribing object living forever in the memory. So, you must detach the event handler of the static event, if your subscriber object is supposed to be destroyed before the lifetime of the application.
Many times, a WPF Window, or Page or User Control subscribes to an event of a ViewModel. And also a ViewModel is highly likely to be living way longer than a view. In such cases, if your view subscribes to an event of a ViewModel, then, be very careful and remember to unsubscribe the event as soon as possible. In many source base, a view is removed from the layout dynamically, from code behind. In those cases, if the event handlers are not unsubscribed, the views will stay in memory. A View is usually highly memory hungry. So, your application will suffer performance,
So, we learned that an event subscriber injects it's a reference to the event publisher. And in that way, the event publisher protects the event subscriber from being collected by the Garbage Collector. When you start subscribing to an event, use the following flow chart to find out if you should worry about unsubscribing the event or not.