The .NET weak event pattern in C#

Introduction As you may know event handlers are a common source of memory leaks caused by the persistence of objects that are not used anymore, and you may think should have been collected, but are not, and for good reason. &#8230; Continue reading &#8594;

Introduction

As you may know event handlers are a common source of memory leaks caused by the persistence of objects that are not used anymore, and you may think should have been collected, but are not, and for good reason.

In this (hopefully) short article, I’ll present the issue with event handlers in the context of the .Net framework, then I’ll show you how you can implement the standard solution to this issue, the weak event pattern, in two ways, either using:

the “legacy” (well, before .Net 4.5, so not that old) approach which is quite cumbersome to implement

the new approach provided by the .Net 4.5 framework which is as simple as it can be

Not rocket science but it deserves a little explanation if you’re not familiar with this pattern:

first GC.Collect() triggers the .Net CLR garbage-collector which will take care of sweeping unused objects, and for objects whose class has no finalizer (a.k.a destructor in C#) it’s enough

GC.WaitForPendingFinalizers() waits for the finalizers of other objects to execute; we need it because as you’ll see we’ll use the finalizer methods to know when our objects are collected

second GC.Collect() ensures the newly finalized objects are swept too.

The issue

So first thing first, let’s try to understand what’s the problem with event listeners, with the help of some theory and, most importantly, a demo.

Background

When an object acting as an event listener registers one of its instance methods as an event handler on an object that produces events (the event source), the event source must keep a reference to the event listener object in order to raise the event in the context of this listener.
This is fair enough, but if this reference is a strong reference then the listener acts as a dependency of the event source and can’t be garbage-collected even if the last object referencing it is the event source.

Here is a detailed diagram of what happens under the hood:

Events handlers issue

This is not an issue if you can control the life time of the listener object as you can unsubscribe from the event source when you don’t need the listener anymore, typically using the disposable pattern.
But if you can’t identify a single point of responsibility for the life time of the listener then you can’t dispose of it in a deterministic manner and you have to rely on the garbage collection process … which will never consider your object as ready for collection as long as the event source is alive!

“EventListener received event.“: this is the consequence of our call to “source.Raise()”; perfect, seems like we’re listening.

“Setting listener to null.“: we nullify the reference that the current local context holds to the event listener object, which should allow garbage collection of the listener.

“Starting GC.“: garbage collection starts.

“GC finished.“: garbage collection ends, but our event listener object has not been reclaimed by the garbage collector, which is proven by the fact its finalizer has not been called

“EventListener received event.“: this is confirmed by the second call to “source.Raise()”, the listener is still alive!

“Setting source to null.“: we nullify the reference to the event source object.

“Starting GC.“: the second garbage collection starts.

“NaiveEventListener finalized.“: this time our naive listener is collected, better late than never.

“GC finished.“: the second garbage collection ends.

Conclusion: indeed there is an hidden strong reference to the listener which prevents the event listener to be collected as long as the event source is not collected!

Hopefully there is a standard solution for this issue: the event source can reference the listener through a weak reference, which won’t prevent collection of the listener even if the source is still alive.

As expected the behavior is the same as the legacy event manager, what more could we ask for?!

Conclusion

As you’ve seen implementing the weak event pattern in .Net is quite straightforward, particularly with .Net 4.5.

If you’re not using .Net 4.5, as the implementation requires some boilerplate code, you may be tempted to not use this pattern and instead directly use the C# language facilities (+= and -=), and see if you have any memory issue, and only if you notice some leaks then make the necessary effort of implementing it.

But with .Net 4.5, as it’s almost free, the plumbing code being managed by the framework, you can really use it in the first place, though it’s a little less cool than the C# syntax “+=” and “-=” but semantics is equally clear, and this is what matters.

I’ve done my best to be technically accurate and avoid any spelling errors but if you catch any typo or mistake, have some issue with the code or have additional questions feel free to let a comment.

Share

About the Author

To make it short I'm an IT trainer specialized in the .Net ecosystem (framework, C#, WPF, Excel addins...).
(I'm available in France and bordering countries, and I only teach in french.)

I like to learn new things, particularly to understand what happens under the hood, and I do my best to share my humble knowledge with others by direct teaching, by posting articles on my blog (pragmateek.com), or by answering questions on forums.

Comments and Discussions

I have a few hundred thousand listeners and want to implement the WeakEvent pattern. However, even after implementing it, it seems the only way to release the listener is to call TriggerGC(), which is quite a degradation on performance. If I leave the TrggerGC() until the very end (after I'm finished with all my listeners), then the WeakEvent pattern doesn't do much; all the thousands of listeners still keep receiving events (which also result in a fairly costly exercise - recalculating a lot of values) and they're released only at the very end.

Maybe this is just an effect of .NET garbage collection which I don't fully understand yet, but I'd appreciate some tips in this regard.

Indeed the goal of the weak event pattern is to avoid preventing the collection of some objects.But it's only useful when a collection occurs.It is only a memory management tool.

And in .Net the GC decides when it should occur by analyzing the memory footprint.
So if you have millions of small objects consuming 100% of the CPU the GC won't notice them if they consume little memory.

You should manually unregister your listeners from the listened objects, or disable them.
They will stay in memory but won't do anything anymore.

Maybe you could tell us more about your use case so that we could find the best solution for this issue.

Yes,It's the problem.
Actually,I thinked about the reason.
and, I wanted to download the exmple source code to check,
but,the url of the source is not available from my environment.
Thanks for your answer,
It's kind of you!

Not available? What's your "environment"?
If you can't download the source code then you'll have to copy-paste from the article itself.
I think all the code I've used is available inside the code samples blocks.
Hope it'll work for you.