2010-02-11 9 views
6

Prendendo il seguente codice, Resharper mi dice che voicesSoFar e voicesNeededMaximum causano "l'accesso a una chiusura modificata". Ho letto di questi, ma quello che mi lascia perplesso è che Resharper suggerisce di risolvere questo problema estraendo le variabili a destra prima della query LINQ. Ma è lì che sono già!Questo codice causa davvero un problema di "accesso alla chiusura modificata"?

Il terminatore si blocca se aggiungo semplicemente int voicesSoFar1 = voicesSoFar subito dopo int voicesSoFar = 0. C'è qualche logica strana che non capisco che rende il suggerimento di Resharper corretto? O c'è un modo per "accedere alle chiusure modificate" in sicurezza in casi come questi senza causare bug?

// 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

Spengo questo avviso di richiamo. Devi certamente stare attento quando modifichi le variabili catturate, ma questo è uno di quei posti in cui una buona conoscenza della lingua supera bruscamente seguendo una regola di tipo meccanico. C'è un motivo per cui C# consente questo: è utile. –

risposta

6

Hai un problema molto sgradevole che deriva dalla mutazione di una variabile esterna all'espressione lambda. Il problema è questo: se si tenta di iterare eligibleVoters due volte (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); } e subito dopo (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }) non sarà possibile vedere la stessa uscita Che non è giusto dal punto di vista di programmazione funzionale

Ecco un metodo di estensione che verrà.. accumulare fino a qualche condizione sulla accumulatore è vero:

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; 
    } 
} 

Usage:

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

Così il sopra dice accumulano voci mentre abbiamo accumulato meno di 300 voti.

Poi, con:

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

output è:

Alice 
Bob 
Catherine 

Alice 
Bob 
Catherine 
+0

C'è un buon modo per gestire questo? –

+0

Sì, scrivere un metodo di estensione diverso. Vedi la mia modifica. – jason

0

Sospetto che la modifica del valore "voicesSoFar" in TakeWhile stia causando il problema.

3

Bene, il messaggio di errore è corretto in quanto che il valore di voicesSoFar non si conserva durante l'operazione. In termini puramente "funzionali" (e le lambda sono davvero progettate per agire funzionalmente), sarà confusionario.

Per esempio, un test interessante sarebbe:

cosa succede se iterare la domanda due volte?

Ad esempio:

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

credo che si può vedere ... 10, null - confondendo. Di seguito sarebbe ripetibile:

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

Se avete bisogno potrebbe naturalmente scrivere un metodo di estensione riutilizzabile che ha preso un lambda.

Problemi correlati