Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Private field captured in anonymous delegate

class A
{
   public event EventHandler AEvent;
}
class B
{
   private A _foo;
   private int _bar;

   public void AttachToAEvent()
   {
      _foo.AEvent += delegate()
      {
         ...
         UseBar(_bar);
         ...
      }
   }
} 

Since delegate captures variable this._bar, does it implicitly hold to the instance of B? Will instance of B be referenced through the event handler and captured variable by an instance of A?

Would it be different if _bar was a local variable of the AttachToAEvent method?

Since in my case an instance of A lives far longer and is far smaller than an instance of B, I'm worried to cause a "memory leak" by doing this.

like image 412
Damir Avatar asked Dec 07 '11 15:12

Damir


People also ask

What is anonymous delegate in C#?

Anonymous methods provide a technique to pass a code block as a delegate parameter. Anonymous methods are the methods without a name, just the body. You need not specify the return type in an anonymous method; it is inferred from the return statement inside the method body.

What will happen if a delegate has a non void return type?

When the return type is not void as above in my case it is int. Methods with Int return types are added to the delegate instance and will be executed as per the addition sequence but the variable that is holding the return type value will have the value return from the method that is executed at the end.

How are an anonymous delegate and a lambda expression related?

Anonymous Method is an inline code that can be used wherever a delegate type is expected. Microsoft introduced Anonymous Methods in C# 2.0 somewhere around 2003. Lambda expression is an anonymous method that you can use to create delegates or expression tree types.

What is the advantage of using anonymous methods?

The advantage of an anonymous function is that it does not have to be stored in a separate file. This can greatly simplify programs, as often calculations are very simple and the use of anonymous functions reduces the number of code files necessary for a program.


2 Answers

Ani's answer is correct. Summarizing and adding some details:

Since the delegate captures variable this._bar, does it implicitly hold to the instance of B?

Yes. "this" is captured.

Will the instance of B be referenced through event handler and captured variable by an instance of A?

Yes.

Would it be different if _bar were a local variable of the AttachToAEvent method?

Yes. In that case the closure object would hold on to the local; the local would be realized as a field of the closure.

Since in my case an instance of A lives far longer and is far smaller than an instance of B, I'm worried to cause "memory leak" by doing this.

You are absolutely right to worry. Your situation is already bad, but in fact the situation can be considerably worse when you have two anonymous functions in play. Right now all anonymous functions in the same local variable declaration space share a common closure, which means that the lifetimes of all closed-over outer variables (including "this") are extended as far as the longest lived of all of them. See my article on the subject for details:

http://blogs.msdn.com/b/ericlippert/archive/2007/06/06/fyi-c-and-vb-closures-are-per-scope.aspx

We hope to fix this in a hypothetical future version of C#; we could be partitioning the closures better instead of creating one big closure. However that is not going to happen any time soon.

Moreover, the "async/await" feature of C# 5 will also likely exacerbate situations in which locals end up living longer than you'd expect. None of us are thrilled with this, but as they say, the perfect is the enemy of the awesome. We have some ideas about how we can tweak the codegen of async blocks to improve the situation, but no promises.

like image 78
Eric Lippert Avatar answered Sep 28 '22 13:09

Eric Lippert


This is easiest understood by looking at the code generated by the compiler, which is similar to:

public void AttachToAEvent()
{
    _foo.AEvent += new EventHandler(this.Handler);
}

[CompilerGenerated]
private void Handler(object sender, EventArgs e)
{
    this.UseBar(this._bar);
}

As can be plainly seen, the delegate created is an instance-delegate (targets an instance method on an object) and must therefore hold a reference to this object instance.

Since delegate captures variable this._bar, does it implicitly hold to the instance of B?

Actually, the anonymous method captures just this (not this._bar). As can be seen from the generated code, the constructed delegate will indeed hold a reference to the B instance. It has to; how else could the field be read on demand whenever the delegate is executed? Remember that variables are captured, not values.

Since in my case an instance of A lives far longer and is far smaller than an instance of B, I'm worried to cause "memory leak" by doing this.

Yes, you have every reason to be. As long as the A instance is reachable, the B event-subscriber will still be reachable. If you don't want to go fancy with weak-events, you need to rewrite this so the handler is unregistered when it is no longer required.

Would it be different if _bar was a local variable of the AttachToAEvent method?

Yes, it would, as the captured variable would then become the bar local rather than this. But assuming that UseBar is an instance-method, your "problem" (if you want tot think of it that way) has just gotten worse. The compiler now needs to generate an event-listener that "remembers" both the local and the containing B object instance.

This is accomplished by creating a closure object and making it (really an instance method of it) the target of the delegate.

public void AttachToAEvent(int _bar)
{
    Closure closure = new Closure();
    closure._bar = _bar;
    closure._bInstance = this;
    _foo.AEvent += new EventHandler(closure.Handler);
}

[CompilerGenerated]
private sealed class Closure
{
    public int _bar;
    public B _bInstance;

    public void Handler(object sender , EventArgs e)
    {
        _bInstance.UseBar(this._bar);
    }
}
like image 28
Ani Avatar answered Sep 28 '22 12:09

Ani