5

这个问题及其答案很好地解释了隐式捕获闭包的概念。但是,我偶尔会看到似乎应该生成有问题的警告的代码,但实际上不会。例如:

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隐式捕获rnd2a2隐式捕获rnd1。但是,我根本没有收到任何警告(链接问题中的代码确实为我生成了它)。这是 ReSharper 的一个错误(v9.2),还是由于某种原因这里没有发生隐式捕获?

4

2 回答 2

4

我认为 Resharper 出于某种原因在这种情况下无法发现隐式捕获的变量。您可以使用一些反汇编程序来验证自己,编译器会使用 rnd1 和 rnd2 生成单个类。你的例子不是很清楚,但让我们以 Eric Lippert 博客文章中的这个例子为例(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 委托捕获了 Expensive 变量,并且在它死亡之前不会被收集。但是(至少对我而言),Resharper 不会就此向您发出警告。虽然不能将其命名为“错误”,但肯定有改进的地方。

于 2016-04-13T21:05:48.780 回答
0

当编译器捕获闭包中匿名方法使用的局部变量时,它通过生成特定于包含委托定义的方法范围的帮助类来实现。每个范围存在一个这样的方法,即使该范围内有多个委托也是如此。请参阅此处的 Eric Lippert 的解释 。

借鉴您的示例,请考虑以下程序:

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() 它调用编译器生成的辅助类的默认构造函数——负责捕获闭包中的局部变量。

这是编译器生成的帮助器类的 IL:

.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

请注意,此帮助程序类具有 rnd1的字段rnd2

IL 级别的“最终”实现F()类似于以下内容:

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 没有警告您在这种情况下发生了隐式捕获,我不知道。

于 2016-04-13T21:11:46.223 回答