简体   繁体   中英

What kind of memory leaks do lambda event handlers cause?

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 (eg 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM