tôi đang điều tra một số vấn đề đối tượng đời kỳ lạ, và đi qua hành vi rất khó hiểu này của biên dịch C#:Hành vi kết hợp đóng này có phải là lỗi trình biên dịch C# không?
Hãy xem xét các lớp thử nghiệm sau đây:
class Test
{
delegate Stream CreateStream();
CreateStream TestMethod(IEnumerable<string> data)
{
string file = "dummy.txt";
var hashSet = new HashSet<string>();
var count = data.Count(s => hashSet.Add(s));
CreateStream createStream =() => File.OpenRead(file);
return createStream;
}
}
Trình biên dịch tạo ra như sau:
internal class Test
{
public Test()
{
base..ctor();
}
private Test.CreateStream TestMethod(IEnumerable<string> data)
{
Test.<>c__DisplayClass1_0 cDisplayClass10 = new Test.<>c__DisplayClass1_0();
cDisplayClass10.file = "dummy.txt";
cDisplayClass10.hashSet = new HashSet<string>();
Enumerable.Count<string>(data, new Func<string, bool>((object) cDisplayClass10, __methodptr(<TestMethod>b__0)));
return new Test.CreateStream((object) cDisplayClass10, __methodptr(<TestMethod>b__1));
}
private delegate Stream CreateStream();
[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public HashSet<string> hashSet;
public string file;
public <>c__DisplayClass1_0()
{
base..ctor();
}
internal bool <TestMethod>b__0(string s)
{
return this.hashSet.Add(s);
}
internal Stream <TestMethod>b__1()
{
return (Stream) File.OpenRead(this.file);
}
}
}
Lớp gốc chứa hai lambdas: s => hashSet.Add(s)
và () => File.OpenRead(file)
. Lần đầu tiên đóng trên biến cục bộ hashSet
, lần thứ hai đóng trên biến cục bộ file
. Tuy nhiên, trình biên dịch tạo ra một lớp thực hiện đóng cửa duy nhất <>c__DisplayClass1_0
có chứa cả hai hashSet
và file
. Kết quả là, đại diện trả lại CreateStream
chứa và giữ nguyên tham chiếu đến đối tượng hashSet
cần phải có sẵn cho GC khi trả lại TestMethod
.
Trong trường hợp thực tế mà tôi đã gặp sự cố này, một đối tượng rất quan trọng (nghĩa là> 100mb) được đính kèm không chính xác.
câu hỏi cụ thể của tôi là:
- Đây có phải là một lỗi? Nếu không, tại sao hành vi này được coi là mong muốn?
Cập nhật:
C# 5 đặc tả 7.15.5.1 nói:
Khi một biến ngoài được tham chiếu bởi một chức năng mang tính chất biến bên ngoài được cho là đã bị bắt bởi hàm ẩn danh. Thông thường, tuổi thọ của một biến cục bộ được giới hạn ở mức thực hiện khối hoặc câu lệnh mà nó được liên kết (§5.1.7). Tuy nhiên, tuổi thọ của một biến ngoài bị bắt là được kéo dài ít nhất cho đến khi cây đại diện hoặc biểu thức được tạo từ chức năng ẩn danh sẽ đủ điều kiện để thu thập rác.
Điều này dường như được mở ở một mức độ giải thích nào đó và không cấm rõ ràng lambda từ việc nắm bắt các biến mà nó không tham chiếu. Tuy nhiên, this question bao gồm một kịch bản có liên quan, mà @ eric-lippert được coi là lỗi. IMHO, tôi thấy việc thực hiện đóng cửa kết hợp được cung cấp bởi trình biên dịch là một tối ưu hóa tốt, nhưng tối ưu hóa không nên được sử dụng cho lambdas mà trình biên dịch có thể phát hiện hợp lý có thể có tuổi thọ vượt quá khung ngăn xếp hiện tại.
- Làm thế nào để mã chống lại điều này mà không từ bỏ việc sử dụng các lambdas tất cả lại với nhau? Đáng chú ý là làm thế nào để tôi mã chống lại điều này phòng thủ, để thay đổi mã trong tương lai không đột nhiên gây ra một số lambda không thay đổi khác trong cùng một phương pháp để bắt đầu kèm theo một cái gì đó mà nó không nên?
Cập nhật:
Đoạn mã ví dụ tôi đã cung cấp là cần thiết bởi contrived. Rõ ràng, tái cấu trúc lambda tạo ra một phương pháp riêng biệt làm việc xung quanh vấn đề. Câu hỏi của tôi không có ý định về các phương pháp hay nhất về thiết kế (cũng được đề cập bởi @ peter-duniho). Thay vào đó, với nội dung của TestMethod
như nó là viết tắt, tôi muốn biết nếu có bất kỳ cách nào để ép buộc trình biên dịch để loại trừ các lambda createStream
từ việc thực hiện đóng cửa kết hợp.
Đối với hồ sơ, tôi nhắm mục tiêu NET 4.6 với VS 2015.
Họ chia sẻ cùng phạm vi từ vựng. có lẽ vì điều đó. –
Bản sao có thể có của [Các phương thức ẩn danh riêng biệt chia sẻ một lớp?] (Http://stackoverflow.com/questions/3885106/discrete-anonymous-methods-sharing-a-class). Như là một tiền thưởng thêm, ví dụ này là khá đơn giản, nhưng đã được * không * contrived. – Brian
Đây có phải là lý do cho "đóng cửa hoàn toàn bị bắt" không?Tôi nghĩ rằng tôi hiểu rằng cảnh báo tốt hơn rất nhiều ngay bây giờ. Tôi luôn tự hỏi tại sao trong một số trường hợp một lambda đã bắt được một cái gì đó nó không có gì để làm với. –