Why does ReSharper tell me "implicitly captured closure"?
The warning tells you that the variables end
and start
stay alive as any of the lambdas inside this method stay alive.
Take a look at the short example
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();
}
I get an "Implicitly captured closure: g" warning at the first lambda. It is telling me that g
cannot be garbage collected as long as the first lambda is in use.
The compiler generates a class for both lambda expressions and puts all variables in that class which are used in the lambda expressions.
So in my example g
and i
are held in the same class for execution of my delegates. If g
is a heavy object with a lot of resources left behind, the garbage collector couldn't reclaim it, because the reference in this class is still alive as long as any of the lambda expressions is in use. So this is a potential memory leak, and that is the reason for the R# warning.
@splintor As in C# the anonymous methods are always stored in one class per method there are two ways to avoid this:
Use an instance method instead of an anonymous one.
Split the creation of the lambda expressions into two methods.
The warning is valid and displayed in methods that have more than one lambda, and they capture different values.
When a method that contains lambdas is invoked, a compiler-generated object is instantiated with:
- instance methods representing the lambdas
- fields representing all values captured by any of those lambdas
As an example:
class DecompileMe
{
DecompileMe(Action<Action> callable1, Action<Action> callable2)
{
var p1 = 1;
var p2 = "hello";
callable1(() => p1++); // WARNING: Implicitly captured closure: p2
callable2(() => { p2.ToString(); p1++; });
}
}
Examine the generated code for this class (tidied up a little):
class DecompileMe
{
DecompileMe(Action<Action> callable1, Action<Action> callable2)
{
var helper = new LambdaHelper();
helper.p1 = 1;
helper.p2 = "hello";
callable1(helper.Lambda1);
callable2(helper.Lambda2);
}
[CompilerGenerated]
private sealed class LambdaHelper
{
public int p1;
public string p2;
public void Lambda1() { ++p1; }
public void Lambda2() { p2.ToString(); ++p1; }
}
}
Note the instance of LambdaHelper
created stores both p1
and p2
.
Imagine that:
callable1
keeps a long-lived reference to its argument,helper.Lambda1
callable2
does not keep a reference to its argument,helper.Lambda2
In this situation, the reference to helper.Lambda1
also indirectly references the string in p2
, and this means that the garbage collector will not be able to deallocate it. At worst it is a memory/resource leak. Alternatively it may keep object(s) alive longer than otherwise needed, which can have an impact on GC if they get promoted from gen0 to gen1.
For Linq to Sql queries, you may get this warning. The lambda's scope may outlive the method due to the fact that the query is often actualized after the method is out of scope. Depending on your situation, you may want to actualize the results (i.e. via .ToList()) within the method to allow for GC on the method's instance vars captured in the L2S lambda.
Agreed with Peter Mortensen.
The C# compiler generates only one type that encapsulates all variables for all lambda expressions in a method.
For example, given the source code:
public class ValueStore
{
public Object GetValue()
{
return 1;
}
public void SetValue(Object obj)
{
}
}
public class ImplicitCaptureClosure
{
public void Captured()
{
var x = new object();
ValueStore store = new ValueStore();
Action action = () => store.SetValue(x);
Func<Object> f = () => store.GetValue(); //Implicitly capture closure: x
}
}
The compiler generates a type looks like :
[CompilerGenerated]
private sealed class c__DisplayClass2
{
public object x;
public ValueStore store;
public c__DisplayClass2()
{
base.ctor();
}
//Represents the first lambda expression: () => store.SetValue(x)
public void Capturedb__0()
{
this.store.SetValue(this.x);
}
//Represents the second lambda expression: () => store.GetValue()
public object Capturedb__1()
{
return this.store.GetValue();
}
}
And the Capture
method is compiled as:
public void Captured()
{
ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2();
cDisplayClass2.x = new object();
cDisplayClass2.store = new ValueStore();
Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0));
Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1));
}
Though the second lambda does not use x
, it cannot be garbage collected as x
is compiled as a property of the generated class used in the lambda.