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.
http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach –
TL; DR risposta: aggiungi .ToList() o .ToArray() alla fine della tua espressione di query e si sbarazza dell'avviso – JoelFan