【问题标题】:Why *doesn't* ReSharper tell me “implicitly captured closure”?为什么*不* ReSharper 告诉我“隐式捕获的闭包”?
【发布时间】:2016-08-05 03:24:27
【问题描述】:

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),还是由于某种原因这里没有发生隐式捕获?

【问题讨论】:

    标签: c# lambda closures resharper resharper-9.2


    【解决方案1】:

    我认为 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 不会就此向您发出警告。虽然不能将其命名为“错误”,但肯定有改进的地方。

    【讨论】:

    • 我也没有收到关于该示例的警告,但您是正确的,检查表明隐式捕获确实正在发生(对于我的原始代码和您的答案中的代码)
    【解决方案2】:

    当编译器捕获闭包中匿名方法使用的局部变量时,它通过生成一个特定于包含委托定义的方法范围的帮助类来实现。每个范围存在一个这样的方法,即使该范围内有多个委托也是如此。请参阅 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/'&lt;&gt;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
    

    请注意,此帮助程序类具有 rnd1rnd2 的字段。

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

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

    【讨论】:

    • 我认为 R# 一定认为这是正在发生的事情,但是如果我在调试器中检查或反向编译,即使使用我的原始代码,我也会看到类似于您的第二种情况。也许微软改变了 lambda 的编译方式,但我的(有点过时的)R# 版本没有意识到这一点?
    • 我会尽快用一些 IL 细节更新我的答案,但我认为它的长短都包含在@Evk 的答案中。
    猜你喜欢
    • 2012-11-17
    • 2013-09-22
    • 2012-10-08
    • 2013-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多