2016-04-13 3 views
5

This questionとその回答は、暗黙的にキャプチャされたクロージャの概念を非常にうまく説明しています。しかし、実際にはそうではないという警告を生成するようなコードが表示されることがあります。たとえば:*なぜ* ReSharperは私に "暗黙的にクローズドキャプチャ"を教えていないのですか?

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)のバグですか、何らかの理由でここで暗黙のキャプチャが行われていませんか?

答えて

3

私は何らかの理由でResharperが暗黙のうちにキャプチャされた変数を見つけられないと思います。コンパイラがrnd1とrnd2の両方で単一クラスを生成するいくつかの逆アセンブラで自分自身を確認することができます。あなたの例で、それはクリスタルクリアではないですが、のは、彼は危険な暗黙的な取得の一例を説明するエリックリペットのブログ記事(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デリゲートは高価な変数の上に捕捉していることは明らかだし、死ぬまで収集されません。しかし(少なくとも私のために)、Resharperはこれについてあなたに警告しません。しかし、それを「バグ」と名付けることはできませんが、確かに改善の余地があります。

+0

私もその例は警告されていませんが、検査で暗黙のキャプチャが実際に発生していることが正しいことがわかりました(元のコードと回答のコードの両方) – dlf

0

コンパイラがクロージャの匿名メソッドで使用されるローカル変数をキャプチャすると、デリゲート定義を含むメソッドのスコープに固有のヘルパークラスが生成されます。そのスコープに複数の代理人がいる場合でも、スコープごとに1つのメソッドが存在します。 Eric Lippertの説明hereを参照してください。

.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 

注:私たちはF()の実現のために以下を参照してくださいコンパイラによって生成されたILのための外観を撮影

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命令:IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor()コンパイラが生成したヘルパークラスのデフォルトコンストラクタを呼び出しています。これは、ローカル変数をキャプチャする責任を負うものですクローズのレこのヘルパークラスは、両方rnd1rnd2のフィールドを持っていることを

.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 

お知らせ:

はここで、コンパイラが生成するヘルパークラスのILです。 IL-レベルでF()

"最終" の実装は次のようである:

ClosureHelperのに似実装さ
public static void F() 
{ 
    var closureHelper = new ClosureHelper(); 
    closureHelper.rnd1 = new Random(); 
    closureHelper.rnd2 = new Random(); 
    Action a1 = closureHelper.MethodOne; 
    Action a2 = closureHelper.MethodTwo; 
} 

:ReSharperのはしない理由としては

internal class Program 
{ 
    public class ClosureHelper 
    { 
     public Random rnd1; 
     public Random rnd2; 

     void MethodOne() 
     { 
       Program.G(rnd1); 
     } 

     void MethodTwo() 
     { 
       Program.G(rnd2); 
     } 
    } 
} 

この場合暗黙のキャプチャが発生していることを警告します。わかりません。

+0

R#は、デバッガや逆コンパイルを調べると、元のコードでも2番目のケースのように見えます。 Microsoftがラムダのコンパイル方法を変更したのかもしれないが、R#の私の(幾分古くなった)バージョンはそれを知らないだろうか? – dlf

+0

ええ、私は@Evkがこれについて正しいと思います。 drat。 – AGB

+0

私はすぐにいくつかのILの詳細で私の答えを更新しようとしますが、それの長短は@ Evkの答えに含まれていると思います。 – AGB

関連する問題