为什么*不* ReSharper 告诉我“隐式捕获的闭包”?
Why *doesn't* ReSharper tell me “implicitly captured closure”?
This question 及其答案很好地解释了隐式捕获闭包的概念。但是,我偶尔会看到代码似乎应该生成有问题的警告,但实际上并没有。例如:
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)
{
}
我的预期是我会被警告 a1
隐式捕获 rnd2
,而 a2
隐式捕获 rnd1
。但是,我根本没有收到任何警告(尽管链接问题中的代码确实为我生成了它)。这是 ReSharper (v9.2) 方面的错误,还是由于某种原因这里没有发生隐式捕获?
我认为 Resharper 出于某种原因无法在这种情况下发现隐式捕获的变量。您可以使用一些反汇编程序验证您自己,编译器生成带有 rnd1 和 rnd2 的单个 class。对于您的示例,crystal 不清楚,但让我们以 Eric Lippert 博客 post (https://blogs.msdn.microsoft.com/ericlippert/2007/06/06/fyi-c-and-vb-closures-are-per-scope/) 中的这个示例为例,他在其中描述了一个危险的隐式捕获示例:
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 {
}
这里很明显,longlived delegate 捕获了 Expensive 变量,并且在它死亡之前不会被收集。但是(至少对我而言),Resharper 不会就此警告您。虽然无法命名 "bug",但肯定有改进的地方。
当编译器捕获闭包中匿名方法使用的局部变量时,它通过生成一个特定于包含委托定义的方法范围的帮助程序 class 来实现。每个作用域都存在一种这样的方法,即使该作用域中有多个委托也是如此。请参阅 Eric Lippert 的解释 here。
借用您的示例,考虑以下程序:
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)
{
}
}
}
查看编译器生成的 IL,我们看到下面是 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
注意第一条 IL 指令:IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor()
调用编译器生成的帮助程序 class 的默认构造函数——负责捕获闭包中的局部变量。
这是编译器生成的帮助程序的 IL 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
请注意,此助手 class 具有 rnd1
和 rnd2
.
的字段
F()
在 IL 级别的 "final" 实现类似于以下内容:
public static void F()
{
var closureHelper = new ClosureHelper();
closureHelper.rnd1 = new Random();
closureHelper.rnd2 = new Random();
Action a1 = closureHelper.MethodOne;
Action a2 = closureHelper.MethodTwo;
}
其中 ClosureHelper
的实现类似于:
internal class Program
{
public class ClosureHelper
{
public Random rnd1;
public Random rnd2;
void MethodOne()
{
Program.G(rnd1);
}
void MethodTwo()
{
Program.G(rnd2);
}
}
}
至于为什么 ReSharper 没有警告您在这种情况下发生了隐式捕获,我不知道。
This question 及其答案很好地解释了隐式捕获闭包的概念。但是,我偶尔会看到代码似乎应该生成有问题的警告,但实际上并没有。例如:
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)
{
}
我的预期是我会被警告 a1
隐式捕获 rnd2
,而 a2
隐式捕获 rnd1
。但是,我根本没有收到任何警告(尽管链接问题中的代码确实为我生成了它)。这是 ReSharper (v9.2) 方面的错误,还是由于某种原因这里没有发生隐式捕获?
我认为 Resharper 出于某种原因无法在这种情况下发现隐式捕获的变量。您可以使用一些反汇编程序验证您自己,编译器生成带有 rnd1 和 rnd2 的单个 class。对于您的示例,crystal 不清楚,但让我们以 Eric Lippert 博客 post (https://blogs.msdn.microsoft.com/ericlippert/2007/06/06/fyi-c-and-vb-closures-are-per-scope/) 中的这个示例为例,他在其中描述了一个危险的隐式捕获示例:
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 {
}
这里很明显,longlived delegate 捕获了 Expensive 变量,并且在它死亡之前不会被收集。但是(至少对我而言),Resharper 不会就此警告您。虽然无法命名 "bug",但肯定有改进的地方。
当编译器捕获闭包中匿名方法使用的局部变量时,它通过生成一个特定于包含委托定义的方法范围的帮助程序 class 来实现。每个作用域都存在一种这样的方法,即使该作用域中有多个委托也是如此。请参阅 Eric Lippert 的解释 here。
借用您的示例,考虑以下程序:
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)
{
}
}
}
查看编译器生成的 IL,我们看到下面是 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
注意第一条 IL 指令:IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor()
调用编译器生成的帮助程序 class 的默认构造函数——负责捕获闭包中的局部变量。
这是编译器生成的帮助程序的 IL 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
请注意,此助手 class 具有 rnd1
和 rnd2
.
F()
在 IL 级别的 "final" 实现类似于以下内容:
public static void F()
{
var closureHelper = new ClosureHelper();
closureHelper.rnd1 = new Random();
closureHelper.rnd2 = new Random();
Action a1 = closureHelper.MethodOne;
Action a2 = closureHelper.MethodTwo;
}
其中 ClosureHelper
的实现类似于:
internal class Program
{
public class ClosureHelper
{
public Random rnd1;
public Random rnd2;
void MethodOne()
{
Program.G(rnd1);
}
void MethodTwo()
{
Program.G(rnd2);
}
}
}
至于为什么 ReSharper 没有警告您在这种情况下发生了隐式捕获,我不知道。