Are your Lambdas Leaking?

The problem
At a recent C# Xamarin meetup (I’m a meetup junky) a statement was made from a very knowledgeable individual – when a lambda expression is wired up to an event if a reference is not created to that lambda there is in no way it can be unhooked from the event. Thus, a resource leak has been created that cannot be rectified. This reference of course needs to be on the heap. I was familiar with the leaking lambda problem though even now I occasionally forget to release them – it’s an easy thing to do. I was however skeptical of the statement – the resource can’t be recovered – this is managed code after all. We discussed the usage of reflection and hunting down the Multicast delegates and he said he had gone that route and a reference to the wayward lambda could not be found. I had written a code project article a few years prior regarding reflection, delegates, and events. Lambdas however had not arrived on the scene at the time. Now I have enormous respect for this individual but still I couldn’t shake the feeling that there was a solution. I went home and jumped on the internet trolling for a solution (stack overflow you snarky bitch). Nothing. Still, I knew it could be done and I set to task. Below and attached is my solution.
The target event
First off we need an event to subscribe and unsubscribe to. We’ll define a very simple signature for our event – it simply passes an integer value to our subscribers when fired…
1 |
public delegate void GenericEventSignature(int iValue); |
and the corresponding event declartion
1 |
public event GenericEventSignature GenericEvent; |
We are good to go. Next we’ll add some subscribers – named and anonymous – and then remove them using reflection.
Unsubscribing via reflection
We begin by adding a subscription to the event using a non-anonymous function. First we declare a named function to subscribe
1 2 3 4 |
void NamedMethodSubscriber(int iValue) { Console.WriteLine("Named method subscriber invoked."); } |
Next we subscribe our named function to our event. Assuming the class that owns the event is called EventSource the code is as follows.
1 2 3 |
EventSource m_EventSource = new EventSource(); m_EventSource.GenericEvent += NameMethodSubscriber; |
In addition to the named function we’ll need to add a Lambda subscriber. A simple function that prints out to the command line will do.
1 2 3 4 5 6 7 |
GenericEvent += (i) => { Console.WriteLine("Internal lambda subscriber"); //Note: Compiler will most likely generate a private static field for this delegate. // If this lambda creates a closure then it may cause a resource leak // if the private static field is not nulled. }; |
Now let’s use reflection to unhook this event from it’s subscribers. Armed with the knowledge that each event on an object generates a corresponding private field we take the steps below.
From the target object we need to extract the events and their corresponding fields and pair them off…
1 2 3 4 5 6 7 8 9 10 |
//1) Set binding flags and get type of the object that has the events (event source) BindingFlags instanceBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; Type objectType = m_EventSource.GetType(); //2) Extract the events and fields and pair them off EventInfo[] events = objectType.GetEvents(instanceBindingFlags); FieldInfo[] instanceFields = objectType.GetFields(instanceBindingFlags); var eventFields = from f in instanceFields join e in events on f.Name equals e.Name select new { f, e }; |
Once we have events paired off with their fields we cycle through all the fields, get their associated MulticastDelegate objects, and unsubscribe each subscriber that appears in the MulitcastDelegate’s invovation list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//3) Cycle through the event fields and unsubscribe all subscribers eventFields.ToList().ForEach(eventField => { MulticastDelegate md = eventField.f.GetValue(m_EventSource) as MulticastDelegate; if (md != null) { Delegate[] eventSubscribers = md.GetInvocationList(); if (eventSubscribers != null) { eventSubscribers.ToList().ForEach(eventSubscriber => { eventField.e.RemoveEventHandler(m_EventSource, eventSubscriber); }); } } }); |
The need for pairing the events and the fields is that the field holds the list of subscribers – the MulticastDelegate – but the unsubcribe method, “RemoveEventHandler”, is a method on the event object.
Lambda Caveat
The target event has now been purged of subscribers – even those that we’re declared as anonymous. There is a catch though – a potential leak that is insidious in nature. To understand the problem we need to delve into how Lambda’s are allocated and stored within our program. They may be anonymous but they need to reside somewhere. When a Lambda is created a private field is generated and attached to our event source’s type. In the case of our example the type is “EventSource”. As we know static fields are not subject to garbage collection. If a given Lambda instance should reference a variable (e.g. create a closure) there is a very strong potential of creating a resource leak. To rectify this I see two alternatives.
A) Define a “magic string” style input such that when broadcast to subscribers they will give up any references they acquired during instantiation. Far from ideal.
B) Cycle through the private static fields of the event source type looking for those that derive from Delegate. For each field encountered null out it’s reference. A shotgun approach. The code to accomplish this is as follows…
1 2 3 4 5 6 7 8 9 10 11 |
//4) The compiler may elect to create static private fields on the object for lambdas it detects. // If static fields have been inserted by the compiler and they have created a closure they can cause a resource leak FieldInfo[] staticFields = objectType.GetFields(BindingFlags.Static | BindingFlags.NonPublic); foreach (FieldInfo staticField in staticFields) { Delegate fieldInstance = staticField.GetValue(null) as Delegate; if (fieldInstance != null) { staticField.SetValue(null, null); } } |
A final note for completeness: For the purposes of this article I’ve treated anonymous functions and Lambdas as one in the same. In actual fact there is a very subtle difference. I won’t go into it here but for those that are interested please see this blog post.
The full sample code that served as the basis for this article can be found here