When using normal C# events, registering an event handler creates a strong reference from the event source to the listening object.

If the source object has a longer lifetime than the listener, and the listener doesn't need the events anymore when there are no other references to it, using normal .NET events causes a memory leak: the source object holds listener objects in memory that should be garbage collected.

There are lots of different approaches to this problem. This article will explain some of them and discuss their advantages and disadvantages. I have sorted the approaches in two categories: first, we will assume that the event source is an existing class with a normal C# event; after that, we will allow modifying the event source to allow different approaches.

Many programmers think events are a list of delegates - that's simply wrong. Delegates themselves have the ability to be "multi-cast":

EventHandler eh = Method1;
eh += Method2;

So, what then are events? Basically, they are like properties: they encapsulate a delegate field and restrict access to it. A public delegate field (or a public delegate property) could mean that other objects could clear the list of event handlers, or raise the event - but we want only the object defining the event to be able to do that.

Properties essentially are a pair of get/set-methods. Events are just a pair of add/remove-methods.

publicevent EventHandler MyEvent {
add { ... }
remove { ... }
}

Only adding and removing handlers is public. Other classes cannot request the list of handlers, cannot clear the list, or cannot call the event.

Now, what leads to confusion sometimes is that C# has a short-hand syntax:

Yes, the default C# events are locking on this! You can verify this with a disassembler - the add and remove methods are decorated with [MethodImpl(MethodImplOptions.Synchronized)], which is equivalent to locking on this.

Registering and deregistering events is thread-safe. However, raising the event in a thread-safe manner is left to the programmer writing the code that raises the event, and often gets done incorrectly: the raising code that's probably used the most is not thread-safe:

if (MyEvent != null)
MyEvent(this, EventArgs.Empty);
// can crash with a NullReferenceException
// when the last event handler is removed concurrently.

The second most commonly seen strategy is first reading the event delegate into a local variable.

EventHandler eh = MyEvent;
if (eh != null) eh(this, EventArgs.Empty);

Is this thread-safe? Answer: it depends. According to the memory model in the C# specification, this is not thread-safe. The JIT compiler is allowed to eliminate the local variable, see Understand the Impact of Low-Lock Techniques in Multithreaded Apps [^]. However, the Microsoft .NET runtime has a stronger memory model (starting with version 2.0), and there, that code is thread-safe. It happens to be also thread-safe in Microsoft .NET 1.0 and 1.1, but that's an undocumented implementation detail.

A correct solution, according to the ECMA specification, would have to move the assignment to the local variable into a lock(this) block or use a volatile field to store the delegate.

Simple and effective, this is what you should use when possible. But, often, it's not trivially possible to ensure the DeregisterEvent method is called whenever the object is no longer in use. You might try the Dispose pattern, though that's usually meant for unmanaged resources. A finalizer will not work: the garbage collector won't call it because the event source still holds a reference to our object!

Now, we don't require that someone tells us when the listener is no longer in use: it just checks this itself when the event is called. However, if we cannot use solution 0, then usually, it's also not possible to determine "InUse" from within the listener object. And given that you are reading this article, you've probably come across one of those cases.

But, this "solution" already has an important disadvantage over solution 0: if the event is never fired, then we'll leak listener objects. Imagine that lots of objects register to a static "SettingsChanged" event - all these objects cannot be garbage collected until a setting is changed - which might never happen in the program's lifetime.

Advantages

None.

Disadvantages

Leaks when the event never fires; usually, "InUse" cannot be easily determined.

This solution is nearly identical to the previous, except that we move the event handling code into a wrapper class that forwards the calls to a listener instance which is referenced with a weak reference. This weak reference allows for easy detection if the listener is still alive.

Disadvantages

Note that we stored a reference to the EventWrapper and had a public Deregister method. We can add a finalizer to the listener and use that to deregister from the event.

~ListenerObject() {
ew.Deregister();
}

That should take care of our memory leak, but it comes at a cost: finalizable objects are expensive for the garbage collector. When there are no references to the listener object (except for the weak reference), it'll survive the first garbage collection (and move to a higher generation), have the finalizer run, and then can only be collected after the next garbage collection (of the new generation).

Also, finalizers run on the finalizer thread; this may cause problems if registering/deregistering events on an event source is not thread-safe. Remember, the default events generated by the C# compiler are not thread-safe!

Advantages

Allows garbage collection of the listener object; does not leak wrapper instances.

Disadvantages

The code download contains a reusable version of the wrapper class. It works by taking the lambda expressions for the code parts that need to be adapted to a specific use: Register event handler, deregister event handler, forward the event to a private method.

The returned eventWrapper exposes a single public method: Deregister. Now, we need to be careful with lambda expressions, since they are compiled to delegates that may contain further object references. That's why the event listener is passed back as "me". Had we written (me, sender, args) => this.OnEvent(sender, args), the lambda expression would have captured the "this" variable, causing a closure object to be generated. Since the WeakEventHandler stores a reference to the forwarding delegate, this would have caused a strong reference from the wrapper to the listener. Fortunately, it's possible to check whether a delegate captures any variables: the compiler will generate an instance method for lambda expressions that capture variables, and a static method for lambda expressions that don't. WeakEventHandler checks this using Delegate.Method.IsStatic, and will throw an exception if you use it incorrectly.

This approach is fairly reusable, but it still requires a wrapper class for each delegate type. While you can get pretty far with System.EventHandler and System.EventHandler<T>, you might want to automate this when there are lots of different delegate types. This could be done at compile-time using code generation, or at runtime using System.Reflection.Emit.

Advantages

Disadvantages

WPF has built-in support for listener-side weak events, using the WeakEventManager class. It works similar to the previous wrapper solutions, except that a single WeakEventManager instance serves as a wrapper between multiple sender and multiple listeners. Due to this single instance, the WeakEventManager can avoid the leak when the event is never called: registering another event on a WeakEventManager can trigger a clean-up of old events. These clean-ups are scheduled using the WPF dispatcher, they will occur only on threads running a WPF message loop.

Also, the WeakEventManager has a restriction that our previous solutions didn't have: it requires the sender parameter to be set correctly. If you use it to attach to button.Click, only events with sender==button will be delivered. Some event implementations may simply attach the handlers to another event:

The WeakEventManager also deserves to be mentioned in this section: as a wrapper, it attaches ("listening-side") to normal C# events, but it also provides ("source-side") a weak event to clients.

In the WeakEventManager, this is the IWeakEventListener interface. The listening object implements an interface, and the source simply has a weak reference to the listener and calls the interface method.

This is another approach to weak events used in WPF: CommandManager.InvalidateRequery looks like a normal .NET event, but it isn't. It holds only a weak reference to the delegate, so registering to that static event does not cause memory leaks.

This is a simple solution, but it's easy for event consumers to forget about it and get it wrong:

The problem here is that the CommandManager only holds a weak reference to the delegate, and the listener doesn't hold any reference to it. So, on the next GC run, the delegate will be garbage collected, and OnInvalidateRequery doesn't get called anymore even if the listener object is still in use. To ensure the delegate survives long enough, the listener is responsible for keeping a reference to it.

Advantages

Disadvantages

The SmartWeakEvent in the source code download provides an event that looks like a normal .NET event, but keeps weak references to the event listener. It does not suffer from the "must keep reference to delegate"-problem.

How does it work? Using the Delegate.Target and Delegate.Method properties, each delegate is split up into a target (stored as a weak reference) and the MethodInfo. When the event is raised, the method is invoked using Reflection.

A possible problem here is that someone might try to attach an anonymous method as an event handler that captures a variable.

In this case, the delegate's target object is the closure, which can be immediately collected because there are no other references to it. However, the SmartWeakEvent can detect this case and will throw an exception, so you won't have any difficulty to debug problems because the event handler is deregistered before you think it should be.

How does it work? We're not using Reflection anymore to call the method. Instead, we're compiling a forwarder method (similar to the "forwarding code" in the previous solutions) at runtime using System.Reflection.Emit.DynamicMethod.

Advantages

Looks like a real weak event; nearly no code overhead.

Disadvantages

Does not work in partial trust because it uses reflection on private methods.

Comments and Discussions

What about in a scenario where you want to extremely (I mean, absurdly, extremely) loosely couple publisher from subscriber?

I am working on an "event broker" something along these lines, takes objects (literally, object[]), and reflects for decorated events and for decorated event handler candidates.

When both EventInfo and MethodInfo can be discovered, wires (and/or unwires) the corresponding delegate through the GetAddMethod and GetRemoveMethod, respectively.

Major assumptions are that you have some control over both publisher and subscriber, which has been mentioned in other articles, and can decorate your events and event handling candidates.

What I am interested in is teasing a transparent way of unhooking the event. At least for the moment, my tact I am taking is more along the lines of Part 1 Solution 0: i.e. Just Deregister.

Which goes the same for the publisher as for the subscriber; i.e. what happens if subscriber is more long-lived than publisher. In this case, the broker handles that use case.

Anyhow, interesting article, and interested in some feedback if something like this can be easily monitored, i.e. perhaps wrapping the pub/sub instances in a WeakReference themselves, and/or their delegated handlers.

This is a great class which is not depend on a Dispatcher like the one from WPF.
I had a great thread handle leak (x1000s) of Thread objects because the Dispatcher was not able to cleanup the weak reference list because it was executed on a non ui thread.
This in itself would not be so bad. BUT if you have a long running server you will run out of Desktop heap because the message pump call seems to allocate OS resources which are never released hence every new thread will eat a few KB of your desktop heap until it is full.
This was a very nasty issue to find.
Now I will stick to your class looks much better

Note: This bug report is not valid. See Daniel's reply. I keep the thread here in case someone else is wondering why the Raise method makes copies of the handler array.

Some event handlers might be skipped and some might be called even after they have been removed from the handler list. This is because the Raise method makes a copy of the internal handler list by calling ToArray extension method. By doing this, it completely ignores all additions/removals to the internal handler list, which will cause surprising behavior in applications that register/deregister event handlers from within other event handlers.

In order to fix the issue, I had to replace the internal List with a singly linked list. I've converted EventEntry into a class and gave it Next property to allow chaining. Such linked list of EventEntry instance will behave the same as plain delegates, which AFAIK use similar linked list structure internally.

This is because delegate instances are immutable. When you add/remove a handler, a new delegate instance is created with the new invocation list and assigned to the event's backing field; but the event invocation still proceeds using the original delegate instance.

Beware that the implementation of FastSmartWeakEvent as it currently exists will cause MethodAccessException under certain circumstances. I did not investigate the exact cause, but adding fifth parameter to DynamicMethod constructor solved the issue:

The fifth parameter instructs .NET to completely skip all visibility checks. Apparently there are visibility issues even though the fourth parameter attaches the generated method to the target object, which should enable access to all private methods. I didn't have time to explore this issue further.

Interestingly enough, some of the generated methods threw exceptions while some other generated methods executed just fine. So in my case the exceptions escaped my initial testing and they were discovered only in production.

I think that there is a little leak. The “EventEntry” survive the handler. Its very small but I think that it is possible to have no leak at all but it would request a EventSourceProxy and an EventSourceStub and a weak circular link between the 2. When either of th proxy or stub would be garbage collected, it could advise its counterpart and ensure full clean-up,

The "EventEntry" in the SmartWeakEvent-implementations gets cleaned up when the event is fired, or after a number of other handlers have been added. The latter means that unused EventEntry instances can't accumulate, so it's not really a memory leak. (at least not anymore than the .NET List<T> already "leaks" memory due to the backing array being larger than the contents)

Unfortunately .NET doesn't have any efficient way to be notified of garbage collection to perform additional cleanup - the only way to do so are finalizers, but those prolong the lifetime of the object being finalized (and all referenced objects!) by one generation. If there are many such objects, this will cause more collections in the larger generations, which can be a major performance problem.

I do understand what you say. You code is excellent and I'm using it. I worth a lot to me.

When I wrote that message I had 2 things in mind:

- First: I think it would have been possible to add a new object along with your code. That little object could be instantiate as a member of the object that subscribe to a weak event. That object would have 1 member: a list of method to unsubscribe. It would also implement IDispose and when its owner and itself are GC, on Dispose() would be called and release the little object wrapper of the weak event. (ie: when subscribing, we also add callback method to that little wrapper which is easy to forget, I know).

- Second: it was to take a chance that any Microsoft guy fall on this note and awake an idea in is mind that it would be really nice to create a Weak Event mechanism (perhaps in another new way of eventing that is valid in every cases -weak or not) that is easy to use and have no leak at all in all cases.

There are some small errors that make the article not entirely clear, e.g. "this isn't actually named "_MyEvent" but also "MyEvent"" is ungrammatical.

I'm also not clear why "var h = MyEvent; if (h!=null) h(...)" would ever not be thread safe. Okay, it's not thread safe in the ECMA model of 2001 or whatever, but does anyone use that model? MS.NET does not and never has.

First - you get a 5 abviously
Now, I would suggest expending the Introduction section to include some overview, letting the reader know that the best solution is at the end of the article, as I think that if was close to stop reading half way through it as I did not know in which order solutions were presented (best-to-worst vs worst-to-best etc.) a short "The article concludes with the recommended solution for ....." would do the trick.

Jeffrey Richter has a discussion on this in his book(s) CLR via C#. Unfortunately, as per his blog post on Weak Event Handlers, the published classes he created are incorrect. He has put out a corrected version as of March 11, 2011. The link to the post is:

your article about Weak Events is excellent but there is another problem related to this cause of memory leaks. As GC collects objects in a later time after they have no more references in code, these objects continue receiving event notifications even if we use any weak event implementation. This is an undesirable behaviour obligating programmers to manage the life cycle of all those objects that subscribe events. I think the solution for this could be creating a simple COM class to be used as a base class for all objects that subscribe events. Doing so, all of those objects will have a reference counter. As the framework calls AddRef and Release any time a reference is set or removed for the COM objects, we could override these methods in the base COM object and raise a protected event before the last Release takes place. Doing so, we would have a place and time to do deterministic event deregistering and even the object remaining not collected it will be inactive for all purposes. We do continue needing to implement weak events in combination with this solution cause only doing so the references in the event invocation lists would not polute the reference counting. I'm a newbie to NET Framework and don't have expertise on COM programming so I would like your opinion about this idea.

Events in .NET is a mine field, especially in a model-view-viewmodel application where the risk of leaking view models is really there - if you are not very carefull. Great article and excellent solutions.