Code Inspection: Implicitly captured closure
This inspection draws your attention to the fact that more closure values are being captured than is obviously visibly, which has an impact on the lifetime of these values.
Consider the following code:
In the first closure, we see that both obj1
and obj2
are being explicitly captured; we can see this just by looking at the code. For the second closure, we can see that obj1
is being explicitly captured, but JetBrains Rider is warning us that obj2
is being implicitly captured.
This is due to an implementation detail in the C# compiler. During compilation, closures are rewritten into classes with fields that hold the captured values, and methods that represent the closure itself. The C# compiler will only create one such private class per method, and if more than one closure is defined in a method, then this class will contain multiple methods, one for each closure, and it will also include all captured values from all closures.
If we look at the code that the compiler generates, it looks a little like this (some names have been cleaned up to ease reading):
When the method runs, it creates the display class, which captures all values, for all closures. So even if a value isn't used in one of the closures, it will still be captured. This is the "implicit" capture that JetBrains Rider is highlighting.
The implication of this inspection is that the implicitly captured closure value will not be garbage collected until the closure itself is garbage collected. The lifetime of this value is now tied to the lifetime of a closure that does not explicitly use the value. If the closure is long lived, this might have a negative effect on your code, especially if the captured value is very large.
Note that while this is an implementation detail of the compiler, it is consistent across versions and implementations such as Microsoft (pre and post Roslyn) or Mono's compiler. The implementation must work as described in order to correctly handle multiple closures capturing a value type. For example, if multiple closures capture an int
, then they must capture the same instance, which can only happen with a single shared private nested class. The side effect of this is that the lifetime of all captured values is now the maximum lifetime of any closure that captures any of the values.