我正在調查一些奇怪的對象的生命週期的問題,工作在C#編譯器的這個非常令人費解的行爲傳來:這個閉包組合行爲是一個C#編譯器錯誤嗎?
請看下面的測試類:
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;
}
}
編譯器生成以下內容:
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);
}
}
}
原始類包含兩個lambda:s => hashSet.Add(s)
和() => File.OpenRead(file)
。第一個關閉局部變量hashSet
,第二個關閉局部變量file
。但是,編譯器會生成一個包含hashSet
和file
的封閉實現類<>c__DisplayClass1_0
。因此,返回的CreateStream
委託包含並保持對hashSet
對象的引用,該對象在返回時應該可用於GC一次TestMethod
。
在我遇到此問題的實際場景中,一個非常實際的(即> 100mb)對象被錯誤地包含在內。
我的具體問題是:
- 這是一個錯誤?如果不是,爲什麼這種行爲被認爲是可取的?
更新:
的C#5規格7.15.5.1說:
當外變量由匿名函數引用,則 外變量到所述已被匿名的 功能捕獲。通常,局部變量的生命週期限於 執行與其關聯的塊或語句 (第5.1.7節)。但是,捕獲的外部變量的生存期至少延長至 ,直到從 創建的委託或表達式樹變爲符合垃圾回收條件。
這似乎對某種程度的解釋是開放的,並沒有明確禁止lambda捕獲它沒有引用的變量。但是,this question涵蓋了一個相關的場景,@ eric-lippert被認爲是一個錯誤。恕我直言,我看到由編譯器提供的組合閉包實現作爲一種很好的優化,但是優化不應該用於編譯器可以合理檢測的lambda,可能會超出當前棧幀的壽命。
- 如何針對這一點,我的代碼,而放棄使用lambda表達式一起?值得注意的是,我如何在防守方面進行編碼,以便將來的代碼更改不會在同一方法中突然導致其他一些未更改的lambda開始包含它不應該包含的內容?
更新:
我提供的代碼示例是必然做作。顯然,將lambda創建重構爲單獨的方法可解決該問題。我的問題不是關於設計最佳實踐(也包括@ peter-duniho)。相反,根據TestMethod
的內容,我想知道是否有辦法強制編譯器從組合閉包實現中排除createStream
lambda。
爲了記錄在案,我與VS .NET目標4.6 2015年
他們共享相同的詞彙範圍。也許正是因爲如此。 –
[離散匿名方法共享一個類?]的可能重複(http://stackoverflow.com/questions/3885106/discrete-anonymous-methods-sharing-a-class)。作爲一個額外的好處,這個例子非常簡單,但是*不是*做的。 – Brian
這是「隱性封閉」的原因嗎?我想我明白現在警告好多了。我總是想知道爲什麼在某些情況下,拉姆達捕獲的東西與它無關。 –