2013-02-16 18 views
82

sto ottenendo il seguente avviso:accesso al foreach variabile in chiusura avvertimento

accesso al foreach variabile in chiusura. Potrebbe avere un comportamento diverso quando compilato con diverse versioni del compilatore.

Questo è ciò che sembra nel mio editor:

abovementioned error message in a hover popup

so come risolvere questo avviso, ma io voglio sapere perché vorrei ottenere questo avvertimento?

Si tratta della versione "CLR"? È correlato a "IL"?

+4

http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach –

+1

TL; DR risposta: aggiungi .ToList() o .ToArray() alla fine della tua espressione di query e si sbarazza dell'avviso – JoelFan

risposta

134

Ci sono due parti per questo avviso. Il primo è ...

L'accesso alla variabile foreach in chiusura

... che non è valida di per sé, ma è contro-intuitivo a prima vista. È anche molto difficile fare bene. (Tanto che l'articolo che link qui sotto lo descrive come "dannoso".)

Prendi la tua query, osservando che il codice che hai estratto è fondamentalmente una forma espansa di ciò che il compilatore C# (prima di C# 5) genera per foreach :

I [non] capisco perché [il seguente è] non è valida:

string s; while (enumerator.MoveNext()) { s = enumerator.Current; ... 

Beh, è ​​valida sintatticamente. E se tutto quello che stai facendo nel tuo ciclo utilizza il valore di s, allora tutto va bene. Ma chiudere su s porterà a comportamenti contro-intuitivi. Date un'occhiata al seguente codice:

var countingActions = new List<Action>(); 

var numbers = from n in Enumerable.Range(1, 5) 
       select n.ToString(CultureInfo.InvariantCulture); 

using (var enumerator = numbers.GetEnumerator()) 
{ 
    string s; 

    while (enumerator.MoveNext()) 
    { 
     s = enumerator.Current; 

     Console.WriteLine("Creating an action where s == {0}", s); 
     Action action =() => Console.WriteLine("s == {0}", s); 

     countingActions.Add(action); 
    } 
} 

Se si esegue questo codice, si otterrà il seguente output della console:

Creating an action where s == 1 
Creating an action where s == 2 
Creating an action where s == 3 
Creating an action where s == 4 
Creating an action where s == 5 

Questo è quello che ci si aspetta.

Per vedere qualcosa che probabilmente non ti aspetti, eseguire il seguente codice di immediatamente dopo il codice sopra:

foreach (var action in countingActions) 
    action(); 

Otterrete il seguente output della console:

s == 5 
s == 5 
s == 5 
s == 5 
s == 5 

Perché ? Perché abbiamo creato cinque funzioni che fanno esattamente la stessa cosa: stampa il valore di s (che abbiamo chiuso). In realtà, hanno la stessa funzione ("Stampa s", "Stampa s", "Stampa s" ...).

Nel punto in cui andiamo a usarli, fanno esattamente quello che chiediamo: stampa il valore di s.Se osservi l'ultimo valore noto di s, vedrai che è 5. Quindi otteniamo s == 5 stampato cinque volte nella console.

Che è esattamente quello che abbiamo chiesto, ma probabilmente non quello che vogliamo.

La seconda parte l'avvertimento ...

possono avere un comportamento diverso quando si compila con diverse versioni di compilatore.

... è quello che è. Starting with C# 5, the compiler generates different code that "prevents" this from happening via foreach.

Così il seguente codice produrrà risultati diversi in diverse versioni del compilatore:

foreach (var n in numbers) 
{ 
    Action action =() => Console.WriteLine("n == {0}", n); 
    countingActions.Add(action); 
} 

Di conseguenza, si produrrà anche la R # avvertimento :)

Il mio primo frammento di codice, al di sopra, sarà esibire lo stesso comportamento in tutte le versioni del compilatore, dal momento che non sto usando foreach (piuttosto, ho ampliato il modo in cui i compilatori pre-C# 5 fanno).

È per la versione CLR?

Non sono sicuro di cosa stai chiedendo qui.

Il post di Eric Lippert dice che il cambiamento avviene "in C# 5". Quindi, presumibilmente devi scegliere .NET 4.5 o successivo con un compilatore C# 5 o successivo per ottenere il nuovo comportamento, e ogni cosa prima ottiene il vecchio comportamento.

Ma per essere chiari, è una funzione del compilatore e non della versione di .NET Framework.

Esiste rilevanza con IL?

Codice diverso produce IL diverso, quindi in questo senso ci sono conseguenze per l'IL generato.

foreach è un costrutto molto più comune del codice che hai postato nel tuo commento. Il problema si verifica in genere tramite l'utilizzo di foreach, non tramite l'enumerazione manuale. Ecco perché le modifiche a foreach in C# 5 aiutano a prevenire questo problema, ma non completamente.

+6

Ho effettivamente provato il ciclo foreach su diversi compilatori ottenendo risultati diversi usando lo stesso target (.Net 3.5). Ho usato VS2010 (che a sua volta usa il compilatore associato a .net 4.0 credo) e VS2012 (compilatore .net 4.5 credo). In linea di principio questo significa che se si sta utilizzando VS2013 e si modifica un progetto di targeting .Net 3.5 e lo si sta costruendo su un server di build con un framework leggermente più vecchio installato, si potrebbero vedere diversi risultati del programma sulla macchina rispetto alla build distribuita. – Ykok

+0

Buona risposta, ma non sono sicuro di quanto "foreach" sia pertinente. Non succederebbe questo con l'enumerazione manuale, o anche con un ciclo semplice per (int i = 0; i Brad

+0

Il materiale 'foreach' qui viene dal contenuto della domanda. Hai ragione che può accadere in vari modi, più generali. –

12

La prima risposta è ottima, quindi ho pensato di aggiungere solo una cosa.

Si riceve l'avviso perché, nel codice di esempio, a ReflectedModel viene assegnato un oggetto IEnumerable, che verrà valutato solo al momento dell'enumerazione e l'enumerazione stessa potrebbe avvenire al di fuori del ciclo se si è assegnato ReflectedModel a qualcosa con un ambito più ampio.

Se è stata modificata

...Where(x => x.Name == property.Value)

a

...Where(x => x.Name == property.Value).ToList()

poi reflectedModel verrebbe assegnato un elenco preciso all'interno del ciclo foreach, in modo da non ricevere l'avviso, come il l'enumerazione sarebbe sicuramente avvenuta all'interno del ciclo, e non al di fuori di esso.

+0

Ho letto un sacco di spiegazioni davvero lunghe che non hanno risolto questo problema per me, quindi una breve che l'ha fatto. Grazie! –

+0

Ho letto la risposta accettata e ho pensato "come è una chiusura se non è vincolante le variabili?" ma ora capisco che sta succedendo la valutazione, grazie! – Jerome

+0

Sì, questa è la soluzione universale ovvia. Lento, ad alta intensità di memoria, ma penso che funzioni davvero al 100% per tutti i casi. –

8

Una variabile con ambito di blocco dovrebbe risolvere l'avviso.

foreach (var entry in entries) 
{ 
    var en = entry; 
    var result = DoSomeAction(o => o.Action(en)); 
} 
+0

Breve e al punto. Grazie... – raider33

Problemi correlati