When the compiler captures the local variables used by anonymous methods in closures, it does this by creating a helper class specific to the scope of the method containing the delegate definition. One such method exists for each scope, even if there are several delegates in this scope. See Eric Lippert's Description here .
Borrowing from your example, consider the following program:
using System; namespace ConsoleApplication { internal class Program { private static void Main(string[] args) { F(); } public static void F() { var rnd1 = new Random(); var rnd2 = new Random(); Action a1 = () => G(rnd1); Action a2 = () => G(rnd2); } private static void G(Random r) { } } }
Looking at the IL generated by the compiler, we see the following for the implementation of F() :
.method public hidebysig static void F () cil managed { // Method begins at RVA 0x205c // Code size 56 (0x38) .maxstack 2 .locals init ( [0] class ConsoleApplication.Program/'<>c__DisplayClass1_0' 'CS$<>8__locals0', [1] class [mscorlib]System.Action a1, [2] class [mscorlib]System.Action a2 ) IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor() IL_0005: stloc.0 IL_0006: nop IL_0007: ldloc.0 IL_0008: newobj instance void [mscorlib]System.Random::.ctor() IL_000d: stfld class [mscorlib]System.Random ConsoleApplication.Program/'<>c__DisplayClass1_0'::rnd1 IL_0012: ldloc.0 IL_0013: newobj instance void [mscorlib]System.Random::.ctor() IL_0018: stfld class [mscorlib]System.Random ConsoleApplication.Program/'<>c__DisplayClass1_0'::rnd2 IL_001d: ldloc.0 IL_001e: ldftn instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::'<F>b__0'() IL_0024: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0029: stloc.1 IL_002a: ldloc.0 IL_002b: ldftn instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::'<F>b__1'() IL_0031: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0036: stloc.2 IL_0037: ret } // end of method Program::F
Pay attention to the first IL command: IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor() , which calls the default constructor of the helper class created by the compiler, which is responsible for capturing local variables in the closure.
Here is the IL for the compiler helper class:
.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass1_0' extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Fields .field public class [mscorlib]System.Random rnd1 .field public class [mscorlib]System.Random rnd2 // Methods .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x20a3 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: ret } // end of method '<>c__DisplayClass1_0'::.ctor .method assembly hidebysig instance void '<F>b__0' () cil managed { // Method begins at RVA 0x20ac // Code size 13 (0xd) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld class [mscorlib]System.Random ConsoleApplication.Program/'<>c__DisplayClass1_0'::rnd1 IL_0006: call void ConsoleApplication.Program::G(class [mscorlib]System.Random) IL_000b: nop IL_000c: ret } // end of method '<>c__DisplayClass1_0'::'<F>b__0' .method assembly hidebysig instance void '<F>b__1' () cil managed { // Method begins at RVA 0x20ba // Code size 13 (0xd) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld class [mscorlib]System.Random ConsoleApplication.Program/'<>c__DisplayClass1_0'::rnd2 IL_0006: call void ConsoleApplication.Program::G(class [mscorlib]System.Random) IL_000b: nop IL_000c: ret } // end of method '<>c__DisplayClass1_0'::'<F>b__1' } // end of class <>c__DisplayClass1_0
Note that this helper class has fields for both rnd1 and rnd2 .
The "final" implementation of F() at the IL level is similar to the following:
public static void F() { var closureHelper = new ClosureHelper(); closureHelper.rnd1 = new Random(); closureHelper.rnd2 = new Random(); Action a1 = closureHelper.MethodOne; Action a2 = closureHelper.MethodTwo; }
Where ClosureHelper implemented is akin to:
internal class Program { public class ClosureHelper { public Random rnd1; public Random rnd2; void MethodOne() { Program.G(rnd1); } void MethodTwo() { Program.G(rnd2); } } }
As to why ReSharper does not warn you that an implicit capture occurs in this case, I donβt know.