2012-11-29 56 views
250

我有以下代碼:爲什麼ReSharper告訴我「隱式捕獲閉包」?

public double CalculateDailyProjectPullForceMax(DateTime date, string start = null, string end = null) 
{ 
    Log("Calculating Daily Pull Force Max..."); 

    var pullForceList = start == null 
          ? _pullForce.Where((t, i) => _date[i] == date).ToList() // implicitly captured closure: end, start 
          : _pullForce.Where(
           (t, i) => _date[i] == date && DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 && 
              DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList(); 

    _pullForceDailyMax = Math.Round(pullForceList.Max(), 2, MidpointRounding.AwayFromZero); 

    return _pullForceDailyMax; 
} 

現在,我已經添加了對ReSharper是在暗示改變該行的註釋。這是什麼意思,或爲什麼需要改變?

+6

MyCodeSucks請修復接受的答案:kevingessner的一個錯誤(如評論中所解釋的)並將其標記爲已接受將誤導用戶,如果他們沒有注意到控制檯的答案。 – Albireo

+0

如果你在try/catch之外定義你的列表,並且在try/catch中完成所有的添加,然後將結果設置爲另一個對象,你也可以看到這個。在try/catch中移動定義/添加將允許GC。希望這是有道理的。 –

回答

345

該警告告訴您變量endstart保持活着,因爲此方法內的任何lambda表達式都保持活動狀態。

看看短期例子

protected override void OnLoad(EventArgs e) 
{ 
    base.OnLoad(e); 

    int i = 0; 
    Random g = new Random(); 
    this.button1.Click += (sender, args) => this.label1.Text = i++.ToString(); 
    this.button2.Click += (sender, args) => this.label1.Text = (g.Next() + i).ToString(); 
} 

我得到一個「隱式捕獲關閉:G」警告在第一拉姆達。它告訴我,只要第一個lambda正在使用,g就不能是garbage collected

編譯器爲兩個lambda表達式生成一個類,並將該類中的所有變量放入lambda表達式中。

所以在我的例子中gi是在同一個類中執行我的代表。如果g是一個擁有大量資源的重對象,那麼垃圾回收器無法回收它,因爲只要使用任何lambda表達式,此類中的引用仍然存在。所以這是潛在的內存泄漏,這就是R#警告的原因。

@splintor 如C#中的匿名方法總是存儲在每個方法一類有兩種方法可以避免這一點:

  1. 使用,而不是匿名一個實例方法。

  2. 將lambda表達式的創建拆分爲兩個方法。

+20

什麼是可能的方法來避免這種捕獲? – splintor

+1

感謝這個偉大的答案 - 我瞭解到,即使僅在一個地方使用非匿名方法,也有理由使用非匿名方法。 – ScottRhee

+1

@splintor實例化委託內的對象,或者將其作爲參數傳遞。在上面的例子中,據我所知,期望的行爲實際上是持有對「Random」實例的引用。 – Casey

26

同意Peter Mortensen。

C#編譯器僅生成一個類型,該類型封裝方法中所有lambda表達式的所有變量。

例如,給定源代碼:

public class ValueStore 
{ 
    public Object GetValue() 
    { 
     return 1; 
    } 

    public void SetValue(Object obj) 
    { 
    } 
} 

public class ImplicitCaptureClosure 
{ 
    public void Captured() 
    { 
     var x = new object(); 

     ValueStore store = new ValueStore(); 
     Action action =() => store.SetValue(x); 
     Func<Object> f =() => store.GetValue(); //Implicitly capture closure: x 
    } 
} 

編譯器會生成一種看起來像:

[CompilerGenerated] 
private sealed class c__DisplayClass2 
{ 
    public object x; 
    public ValueStore store; 

    public c__DisplayClass2() 
    { 
    base.ctor(); 
    } 

    //Represents the first lambda expression:() => store.SetValue(x) 
    public void Capturedb__0() 
    { 
    this.store.SetValue(this.x); 
    } 

    //Represents the second lambda expression:() => store.GetValue() 
    public object Capturedb__1() 
    { 
    return this.store.GetValue(); 
    } 
} 

而且Capture方法被編譯爲:

public void Captured() 
{ 
    ImplicitCaptureClosure.c__DisplayClass2 cDisplayClass2 = new ImplicitCaptureClosure.c__DisplayClass2(); 
    cDisplayClass2.x = new object(); 
    cDisplayClass2.store = new ValueStore(); 
    Action action = new Action((object) cDisplayClass2, __methodptr(Capturedb__0)); 
    Func<object> func = new Func<object>((object) cDisplayClass2, __methodptr(Capturedb__1)); 
} 

雖然第二個lambda不使用x,它不能被垃圾收集器編譯爲x被編譯爲lambda中使用的生成類的屬性。

1

對於Linq to Sql查詢,您可能會收到此警告。由於在該方法超出範圍之後查詢經常被實現,所以lambda的範圍可能超過該方法。根據你的情況,你可能想要實現結果(即通過。ToList()),以允許在L2S lambda中捕獲方法的實例變量上的GC。

21

警告是有效的,並在具有多個拉姆達方法顯示,他們捕捉不同的值

當調用包含lambda表達式的方法,編譯器生成的對象被實例化以:表示的lambda 代表所有值

  • 字段由任何那些lambda表達式捕獲

    • 實例方法

    作爲一個例子:

    class DecompileMe 
    { 
        DecompileMe(Action<Action> callable1, Action<Action> callable2) 
        { 
         var p1 = 1; 
         var p2 = "hello"; 
    
         callable1(() => p1++); // WARNING: Implicitly captured closure: p2 
    
         callable2(() => { p2.ToString(); p1++; }); 
        } 
    } 
    

    檢查生成的代碼這個類(整理了一點點):

    class DecompileMe 
    { 
        DecompileMe(Action<Action> callable1, Action<Action> callable2) 
        { 
         var helper = new LambdaHelper(); 
    
         helper.p1 = 1; 
         helper.p2 = "hello"; 
    
         callable1(helper.Lambda1); 
         callable2(helper.Lambda2); 
        } 
    
        [CompilerGenerated] 
        private sealed class LambdaHelper 
        { 
         public int p1; 
         public string p2; 
    
         public void Lambda1() { ++p1; } 
    
         public void Lambda2() { p2.ToString(); ++p1; } 
        } 
    } 
    

    LambdaHelper創建同時存儲p1p2實例。

    想象一下:

    • callable1保持一個長期參照其說法,helper.Lambda1
    • callable2不保留其參數的引用,helper.Lambda2

    在這種情況下,參考helper.Lambda1也間接引用p2中的字符串,這意味着垃圾收集器將無法處理找到它。最壞的情況是內存/資源泄漏。或者,它可能使對象存活的時間比其他需要的時間更長,如果它們從gen0升級到gen1,可能會對GC產生影響。

  • 相關問題