Saya mendapatkan peringatan berikut:
Akses ke variabel foreach di closure. Mungkin memiliki perilaku yang berbeda ketika dikompilasi dengan versi kompilator yang berbeda.
Seperti inilah tampilannya di editor saya:
Saya tahu cara memperbaiki peringatan ini, tetapi saya ingin tahu mengapa saya mendapatkan peringatan ini?
Apakah ini tentang versi "CLR"? Apakah itu terkait dengan "IL"?
Jawaban:
Ada dua bagian dari peringatan ini. Yang pertama adalah ...
... 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
foreach
1 :Yah, itu valid secara sintaksis. Dan jika semua yang Anda lakukan dalam lingkaran Anda menggunakan nilai dari
s
maka semuanya baik. Tetapi penutupans
akan 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 ("Cetaks
", "Cetaks
", "Cetaks
" ...).Pada titik di mana kita akan menggunakannya, mereka melakukan apa yang kita minta: mencetak nilai
s
. Jika Anda melihat nilai terakhir yang diketahui daris
, Anda akan melihatnya5
. Jadi kamis == 5
dicetak lima kali ke konsol.Itulah tepatnya yang kita minta, tapi mungkin bukan yang kita inginkan.
Bagian kedua dari peringatan ...
... adalah apa adanya. Dimulai dengan C # 5, kompilator menghasilkan kode berbeda yang "mencegah" ini terjadi melalui
foreach
.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).Saya tidak begitu yakin apa yang Anda tanyakan di sini.
Posting Eric Lippert mengatakan perubahan terjadi "di C # 5". Begitu
mungkin Anda harus menargetkan .NET 4.5 atau yang lebih barudengan 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.
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 penggunaanforeach
, bukan melalui pencacahan manual. Itulah mengapa perubahanforeach
pada C # 5 membantu mencegah masalah ini, tetapi tidak sepenuhnya.sumber
foreach
hal di sini berasal dari isi pertanyaan. Anda benar bahwa itu bisa terjadi dalam berbagai cara yang lebih umum.Jawaban pertama bagus, jadi saya pikir saya hanya akan menambahkan satu hal.
Anda mendapatkan peringatan karena, dalam kode contoh Anda, reflectModel diberi IEnumerable, yang hanya akan dievaluasi pada saat enumerasi, dan enumerasi itu sendiri dapat terjadi di luar loop jika Anda menetapkan reflectModel ke sesuatu dengan cakupan yang lebih luas .
Jika Anda berubah
...Where(x => x.Name == property.Value)
untuk
...Where(x => x.Name == property.Value).ToList()
kemudian reflectModel akan diberi daftar tertentu dalam loop foreach, jadi Anda tidak akan menerima peringatan, karena enumerasi pasti akan terjadi di dalam loop, dan bukan di luar itu.
sumber
Variabel dengan cakupan blok harus menyelesaikan peringatan.
foreach (var entry in entries) { var en = entry; var result = DoSomeAction(o => o.Action(en)); }
sumber