2010-02-11 32 views
6

以下面的代碼,Resharper告訴我,voicesSoFarvoicesNeededMaximum會導致「訪問修改後的閉包」。我閱讀了這些內容,但這裏令我感到困惑的是,Resharper建議通過在LINQ查詢之前提取變量來解決此問題。但那就是他們已經在哪裏了!這段代碼是否確實導致了「訪問修改後的關閉」問題?

如果我只是在int voicesSoFar = 0之後加上int voicesSoFar1 = voicesSoFar,Resharper會停止抱怨。是否有一些奇怪的邏輯,我不明白這使得Resharper的建議正確?或者有沒有一種方法可以在這些情況下安全地「訪問修改後的關閉」而不會導致錯誤?

// this takes voters while we have less than 300 voices  
int voicesSoFar = 0;  
int voicesNeededMaximum = 300;  
var eligibleVoters = 
    voters.TakeWhile((p => (voicesSoFar += p.Voices) < voicesNeededMaximum)); 
+3

我關閉了這個Resharper警告。在修改捕獲的變量時你一定要小心,但這是那些遵循死記硬背的規則對語言有很好理解的地方之一。 C#允許這樣做是有原因的 - 這很有用。 – 2010-02-11 05:59:10

回答

6

你有一個非常討厭的問題,它是由將外部變量變爲lambda表達式引起的。問題是這樣的:如果你試圖重複eligibleVoters兩次(foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }和後立即(foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); })你不會看到相同的輸出是不正確的,從功能編程的角度

這裏是一個擴展方法會。累積,直到在累加器某些條件爲真:

public static IEnumerable<T> TakeWhileAccumulator<T, TAccumulate>(
    this IEnumerable<T> elements, 
    TAccumulate seed, 
    Func<TAccumulate, T, TAccumulate> accumulator, 
    Func<TAccumulate, bool> predicate 
) { 
    TAccumulate accumulate = seed; 
    foreach(T element in elements) { 
     if(!predicate(accumulate)) { 
      yield break; 
     } 
     accumulate = accumulator(accumulate, element); 
     yield return element; 
    } 
} 

用法:

var eligibleVoters = voters.TakeWhileAccumulator(
         0, 
         (votes, p) => votes + p.Voices, 
         i => i < 300 
        ); 

因此,上述說積累的聲音,而我們已經積累少超過300票。

然後用:

foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); } 
Console.WriteLine(); 
foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); } 

輸出是:

Alice 
Bob 
Catherine 

Alice 
Bob 
Catherine 
+0

有沒有一種很好的方法來處理這個問題? – 2010-02-11 05:20:21

+0

是的,寫一個不同的擴展方法。看我的編輯。 – jason 2010-02-11 05:35:24

0

我懷疑修改TakeWhile的「voicesSoFar」值引起的問題。

3

好了,該錯誤信息是在儘可能多的操作過程中,不會保留的voicesSoFar值正確。在純粹的「功能性」術語中(而lambda實際上是爲了功能而設計的),這會造成混淆。

例如,一個有趣的測試將是:

,如果我重複查詢兩次會發生什麼?

例如:

int count = voters.Count(); 
var first = voters.FirstOrDefault(); 

我相信你能看到...... 10null - 混淆。以下是重複的:

public static IEnumerable<Foo> TakeVoices(
    this IEnumerable<Foo> voices, int needed) 
{ 
    int count = 0; 
    foreach (Foo voice in voices) 
    { 
     if (count >= needed) yield break; 
     yield return voice; 
     count += voice.Voices; 
    } 
} 
.... 
foreach(var voice in sample.TakeVoices(numberNeeded)) { 
    ... 
} 

如果你需要,你當然可以寫了一個lambda可重複使用的擴展方法。

相關問題