Ada dua bagian dari peringatan ini. Yang pertama adalah ...
Akses ke variabel foreach di closure
... yang sebenarnya tidak valid tetapi pada pandangan pertama kontra-intuitif. Juga sangat sulit untuk melakukan yang benar. (Sedemikian rupa sehingga artikel yang saya tautkan di bawah menggambarkan ini sebagai "berbahaya".)
Ambil pertanyaan Anda, perhatikan bahwa kode yang Anda kutipan pada dasarnya adalah bentuk yang diperluas dari apa yang dihasilkan oleh C # compiler (sebelum C # 5) untuk foreach1 :
Saya [tidak] mengerti mengapa [berikut ini] tidak valid:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
Yah, itu valid secara sintaksis. Dan jika semua yang Anda lakukan dalam lingkaran Anda menggunakan nilai dari smaka semuanya baik. Tetapi penutupan sakan mengarah pada perilaku kontra-intuitif. Perhatikan kode berikut:
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);
}
}
Jika Anda menjalankan kode ini, Anda akan mendapatkan keluaran konsol berikut:
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
Inilah yang Anda harapkan.
Untuk melihat sesuatu yang mungkin tidak Anda harapkan, jalankan kode berikut segera setelah kode di atas:
foreach (var action in countingActions)
action();
Anda akan mendapatkan keluaran konsol berikut:
s == 5
s == 5
s == 5
s == 5
s == 5
Mengapa? Karena kita membuat lima fungsi yang semuanya melakukan hal yang persis sama: mencetak nilai s(yang telah kita tutup). Pada kenyataannya, mereka memiliki fungsi yang sama ("Cetak s", "Cetak s", "Cetak s" ...).
Pada titik di mana kita akan menggunakannya, mereka melakukan apa yang kita minta: mencetak nilai s. Jika Anda melihat nilai terakhir yang diketahui dari s, Anda akan melihatnya 5. Jadi kami s == 5dicetak lima kali ke konsol.
Itulah tepatnya yang kita minta, tapi mungkin bukan yang kita inginkan.
Bagian kedua dari peringatan ...
Mungkin memiliki perilaku yang berbeda ketika dikompilasi dengan versi kompilator yang berbeda.
... adalah apa adanya. Dimulai dengan C # 5, kompilator menghasilkan kode berbeda yang "mencegah" ini terjadi melaluiforeach .
Dengan demikian kode berikut akan menghasilkan hasil yang berbeda di bawah versi kompiler yang berbeda:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
Akibatnya, itu juga akan menghasilkan peringatan R # :)
Potongan kode pertama saya, di atas, akan menunjukkan perilaku yang sama di semua versi kompilator, karena saya tidak menggunakan foreach(sebaliknya, saya telah mengembangkannya seperti yang dilakukan oleh kompiler pra-C # 5).
Apakah ini untuk versi CLR?
Saya tidak begitu yakin apa yang Anda tanyakan di sini.
Posting Eric Lippert mengatakan perubahan terjadi "di C # 5". Begitumungkin Anda harus menargetkan .NET 4.5 atau yang lebih baru dengan kompiler C # 5 atau yang lebih baru untuk mendapatkan perilaku baru, dan semua yang sebelumnya mendapatkan perilaku lama.
Tetapi untuk lebih jelasnya, ini adalah fungsi dari kompiler dan bukan versi .NET Framework.
Apakah ada relevansinya dengan IL?
Kode yang berbeda menghasilkan IL yang berbeda sehingga dalam hal ini terdapat konsekuensi untuk IL yang dihasilkan.
1 foreach adalah konstruksi yang jauh lebih umum daripada kode yang Anda posting di komentar Anda. Masalah biasanya muncul melalui penggunaan foreach, bukan melalui pencacahan manual. Itulah mengapa perubahan foreachpada C # 5 membantu mencegah masalah ini, tetapi tidak sepenuhnya.