4

Event handlers can easily lead to memory leaks, because the event's invocation list holds a reference to the event handling instance, so that the event handling instance cannot be garbage collected, if the event source is still alive.

But consider the following code:

public class SomeClass
{
    public event EventHandler SomeEvent;
}
public class MyClass
{
    public MyClass(SomeClass source)
    {
        //VERSION 1
        source.SomeEvent += OnSomeEvent;

        //VERSION 2
        void localHandler(object s, EventArgs args) { Console.WriteLine("some action with(out) any references"); }
        source.SomeEvent += localHandler;

        //VERSION 3
        var x = new object();
        source.SomeEvent += (s, e) => { Console.WriteLine("some event fired, using closure x:" + x.GetHashCode()); };

        //VERSION 4
        source.SomeEvent += (s, e) => { Console.WriteLine("some action without any references"); };
    }

    private void OnSomeEvent(object sender, EventArgs e) 
    {
        //...
    }
}

My assumptions/questions why the different event handling versions may cause a memory leak:

  • Version 1: Because the invocation target clearly references the instance of MyClass.
  • Version 2: Because the reference to localHandler implies a reference to the instance of MyClass - except, maybe, if the code inside localHandler has no references to the instance of MyClass?
  • Version 3: Because the lambda contains a closure, which itself is a reference to the instance of MyClass - or is it?
  • Version 4: Because the lambda does not reference the instance of MyClass, this may not cause a leak?

And, follow-up questions to versions 3 and 4:

  • Where is the "magic helper object" that .Net creates for the lambda/closure stored and does it (always) include a reference that would keep the instance of MyClass alive?
  • If lambda event handlers can leak, they should only be used in scenarios where this is not a problem (e.g. MyClass instances outlive SomeClass instances), because they cannot be removed using -=?

EDIT: This post (with the original title "When can event handlers cause memory leaks?") was suggested as a duplicate, of Why and How to avoid Event Handler memory leaks?, but I disagree, because the question was directed at lambda event handlers specifically. I rephrased the question/title to make this more clear.

mike
  • 1,627
  • 1
  • 14
  • 37
  • Possible duplicate of [Why and How to avoid Event Handler memory leaks?](https://stackoverflow.com/questions/4526829/why-and-how-to-avoid-event-handler-memory-leaks) – Klaus Gütter Jan 17 '19 at 10:37
  • I don't consider it a duplicate, because my question goes beyond the one in your link, asking specifically lambdas as event handlers. – mike Jan 17 '19 at 11:19

1 Answers1

1

Disclaimer: I can't guarantee this is 100% truth - your question is quite deep and I could make a mistake.

However, I hope it will give you some thoughts or directions.

Let's consider this question according CLR memory organization:

Local method variables and method parameters are stored in the method stack frame in memory (except they declared with ref keyword).

Stack stores value types and reference-type variables references which point on objects in heap.

Method stack frame is exists while method execution, and local method variables will dissappear with stack frame after method ended.

Except if local variables were captured one way or another, it also relates to compiler work, you can read about it at Jon Skeet's website:

http://jonskeet.uk/csharp/csharp2/delegates.html#captured.variables

Version 1: OnSomeEvent method is member of MyClass and it will captured by Someclass source instance, until delegates that refers on this method will not be removed from event. So, MyClass instance that was created in constructor, placed in heap and holds this method will not be collected by GC until its method reference will not be removed from event.

Compiler compiles lambda by specific way, please read Implementation example paragraph fully:

https://github.com/dotnet/csharplang/blob/master/spec/conversions.md#anonymous-function-conversions

Version 4: 2 links I provided give a kick lambda will be compiled to MyClass method, which will be captured by SomeClass instance as in Version 1

Version 2: I don't know nuances about how local methods will be compiled, but it should be same as in Version 4 (and, therefore, Version 1).

Version 3: All local variables will be captured by interesting way.

You have 'object x' also, so compiler generated class will be created, which will contain public field public object x; and method which will be translated from your lambda (see Implementation example paragraph).

So, I think in Versions 1,2,4 will be internally the same: MyClass will contain the method which will be used as event handler.

In Version 3 compiler generated class will be created and it will hold your local variable and method translated from lamdba.

Any instance of any class will not be collected by GC until SomeClass event has its method in invocation list.

Woldemar89
  • 682
  • 4
  • 10
  • Thanks for your elaborate response. I guess the key statement is **"Any instance of any class will not be collected by GC until SomeClass event has its method in invocation list."**. And, thanks to your explanation, it is also clear which instance is leaked. – mike Jan 19 '19 at 00:56
  • @mike You are welcome. Yes, this is the key statement and you can read it in answer provided by link in 1st comment under your question also. – Woldemar89 Jan 19 '19 at 06:38