Why * not * ReSharper tell me "implicitly closed closure"?

This question and its answers very well explain the concept of implicitly captured closures. However, I sometimes see code that seems to be generating the warning in question, that this is not actually happening. For instance:

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) { } 

I expected that I would be warned that a1 implicitly captures rnd2 , and a2 implicitly captures rnd1 . However, I don't get any warning at all (the code in the related question really generates it for me, though). Is this a bug in the ReSharper (v9.2) part, or does implicit capture fail for some reason?

+5
source share
2 answers

I think Resharper for some reason cannot detect implicitly captured variables in this case. You can test yourself using some kind of disassembler that the compiler generates a single class with rnd1 and rnd2. With your example this is not crystal clear, but let me take this example from Eric Lippert's blog post ( https://blogs.msdn.microsoft.com/ericlippert/2007/06/06/fyi-c-and-vb-closures-are -per-scope / ), where he describes an example of a dangerous implicit capture:

 Func<Cheap> M() { var c = new Cheap(); var e = new Expensive(); Func<Expensive> shortlived = () => e; Func<Cheap> longlived = () => c; shortlived(); // use shortlived // use longlived return longlived; } class Cheap { } class Expensive { } 

It is clear here that the longlived delegate captures a more expensive variable and it will not be collected until it dies. But (at least for me), Resharper will not warn you about this. I can not call it a β€œmistake”, although, of course, there is room for improvement.

+3
source

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.

0
source

Source: https://habr.com/ru/post/1247073/


All Articles