Logo

quadroid

Home
RĂ©sume
Tech Blog

Implicit captured closures

I wrote a StackOverflow answer some time ago about implicit captured closures (or more specifically about a ReSharper warning concerning an implicit captured closure) which got a lot of attention, so I will compose a post here about implicit captured closures. The original Question can be found here.

So what is a closure?

If you define an anonymous delegate or lambda expression you can use the local variables of the current scope:

void Main()
{
   string key = "Key";
   List items = new List { "Test", "Key" };
   
   items.Count(x => x.Equals(key));
}

The closure allows you to use the local variable key inside the lambda expression, the compiler generates a class which does all the magic, something close to this:

[CompilerGenerated]
private sealed class <>c__DisplayClass1 
{
    public int key ;
    public bool Capturedb__0(string x) 
    { 
       return x.Equals(key)); 
    }
}

While the original code gets modified to something like this:

void Main()
{
   <>c__DisplayClass1 cDisplayClass1 = new <>c__DisplayClass1();
   cDisplayClass1.key = "Key";
   List items = new List { "Test", "Key" };
   
   items.Count(cDisplayClass1.Capturedb__0);
}

You may already see where the problem with implicit captures closures originates as the member variable of a compiler generated class replaces the local scoped variable. To get an implicit captured closure take a look at the following code sniped:

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    int i = 0;
    Random g = new Random();
    this.button1.Click += (sender, args) => this.label1.Text = i++.ToString();
    this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString();
}

This code will generate a closure for both local variables, in the same class. Which is no problem as long as the delegates have the same lifetime. If the lifetime of the delegates differs, both variables stay in scope as long as any lambda is in use. While in my example code, this is not a big deal, it can cause a memory problem if object g would be something more resource intense.

While this may be a very rare problem it is as well very difficult to spot (if you don't use ReSharper, if you use ReSharper you get a warning on every implicit captured closure).