2016-04-13 14 views
5

This question và các câu trả lời của nó giải thích khái niệm về việc đóng kín được chụp hoàn toàn rất tốt. Tuy nhiên, tôi thỉnh thoảng thấy mã mà có vẻ như nó sẽ tạo ra các cảnh báo trong câu hỏi mà trên thực tế thì không. Ví dụ:Tại sao * không * ReSharper cho tôi biết “đóng cửa hoàn toàn bị bắt”?

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

kỳ vọng của tôi là tôi muốn được cảnh báo rằng a1 ngầm chụp rnd2, và a2 ngầm chụp rnd1. Tuy nhiên, tôi không nhận được cảnh báo nào cả (mã trong câu hỏi được liên kết tạo ra nó cho tôi). Đây có phải là một lỗi trên một phần của ReSharper (v9.2), hoặc không nắm bắt tiềm ẩn không xảy ra ở đây vì một lý do nào đó?

Trả lời

3

Tôi nghĩ rằng Resharper vì một số lý do không thể phát hiện các biến bị nắm bắt ngầm trong trường hợp này. Bạn có thể tự kiểm chứng với một số trình phân tách mà trình biên dịch tạo ra lớp đơn với cả rnd1 và rnd2. Với ví dụ của bạn nó không phải là tinh thể rõ ràng, nhưng chúng ta hãy ví dụ này từ Eric Lippert bài viết trên blog (https://blogs.msdn.microsoft.com/ericlippert/2007/06/06/fyi-c-and-vb-closures-are-per-scope/), nơi ông mô tả một ví dụ về nguy hiểm chụp ngầm:

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 { 

} 

Đây rõ ràng là đại biểu longlived chụp qua biến Đắt và nó sẽ không được thu thập cho đến khi nó chết. Nhưng (ít nhất là đối với tôi), Resharper sẽ không cảnh báo bạn về điều này. Tuy nhiên, không thể đặt tên là "bug", nhưng chắc chắn là một nơi để cải thiện.

+0

Tôi cũng không được cảnh báo về ví dụ đó, nhưng bạn chính xác rằng việc kiểm tra cho thấy việc thu thập tiềm ẩn thực sự xảy ra (đối với cả mã gốc và mã trong câu trả lời của bạn) – dlf

0

Khi trình biên dịch nắm bắt các biến cục bộ được sử dụng bởi các phương thức ẩn danh trong phần đóng, nó làm như vậy bằng cách tạo một lớp trợ giúp cụ thể cho phạm vi của phương thức chứa định nghĩa đại biểu. Một phương thức như vậy tồn tại trên mỗi phạm vi, ngay cả khi có nhiều đại biểu trong phạm vi đó. Xem giải thích của Eric Lippert here.

Vay từ ví dụ của bạn, hãy xem xét các chương trình sau đây:

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

Lấy một cái nhìn cho IL được tạo ra bởi trình biên dịch, chúng ta thấy những điều sau đây để thực hiện 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 

Lưu ý hướng dẫn IL đầu tiên: IL_0000: newobj instance void ConsoleApplication.Program/'<>c__DisplayClass1_0'::.ctor() đang gọi hàm tạo mặc định của lớp trình trợ giúp do trình biên dịch tạo ra - lớp có trách nhiệm ghi lại biến cục bộ les trong đóng cửa.

Đây là IL cho lớp helper trình biên dịch tạo:

.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 

Chú ý rằng lớp helper này có các trường cho cảrnd1rnd2.

Các "cuối cùng" thi hành F() tại IL cấp cao nhất là tương tự như sau:

public static void F() 
{ 
    var closureHelper = new ClosureHelper(); 
    closureHelper.rnd1 = new Random(); 
    closureHelper.rnd2 = new Random(); 
    Action a1 = closureHelper.MethodOne; 
    Action a2 = closureHelper.MethodTwo; 
} 

đâu ClosureHelper được thực hiện tương tự như:

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

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

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

Đối với lý do tại sao ReSharper không cảnh báo bạn rằng có một sự bắt giữ ngầm xảy ra trong trường hợp này, tôi không biết.

+0

Tôi nghĩ R # phải nghĩ rằng điều gì đang xảy ra, nhưng nếu tôi kiểm tra trong trình gỡ rối hoặc biên dịch ngược lại, tôi thấy thứ gì đó giống như trường hợp thứ hai của bạn ngay cả với mã ban đầu của tôi. Có lẽ Microsoft đã thay đổi cách lambdas được biên soạn nhưng tôi phiên bản (hơi out-of-date) của R # là không biết gì về điều đó? – dlf

+0

Vâng, tôi nghĩ @Evk là đúng về điều này. drat. – AGB

+0

tôi sẽ cố gắng cập nhật câu trả lời của tôi với một số chi tiết IL sớm, nhưng tôi nghĩ rằng dài và ngắn của nó là những gì được bao gồm trong @ câu trả lời EVK của. – AGB

Các vấn đề liên quan