Saat menggunakan ekspresi lambda atau metode anonim di C #, kita harus waspada terhadap akses ke perangkap penutupan yang dimodifikasi . Sebagai contoh:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Karena penutupan yang dimodifikasi, kode di atas akan menyebabkan semua Where
klausa pada kueri didasarkan pada nilai akhir dari s
.
Seperti yang dijelaskan di sini , ini terjadi karena s
variabel yang dideklarasikan dalam foreach
loop di atas diterjemahkan seperti ini di kompiler:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
bukannya seperti ini:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Seperti yang ditunjukkan di sini , tidak ada keuntungan kinerja untuk mendeklarasikan variabel di luar loop, dan dalam keadaan normal satu-satunya alasan yang dapat saya pikirkan untuk melakukan ini adalah jika Anda berencana untuk menggunakan variabel di luar lingkup loop:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Namun variabel yang didefinisikan dalam satu foreach
loop tidak dapat digunakan di luar loop:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Jadi kompilator mendeklarasikan variabel dengan cara yang membuatnya sangat rentan terhadap kesalahan yang seringkali sulit ditemukan dan didebug, sementara tidak menghasilkan manfaat yang dapat dilihat.
Apakah ada sesuatu yang dapat Anda lakukan dengan foreach
loop dengan cara ini yang Anda tidak bisa jika mereka dikompilasi dengan variabel cakupan dalam, atau apakah ini hanya pilihan sewenang-wenang yang dibuat sebelum metode anonim dan ekspresi lambda tersedia atau umum, dan yang tidak memiliki sudah direvisi sejak saat itu?
String s; foreach (s in strings) { ... }
?foreach
tetapi tentang ekspresi lamda menghasilkan kode yang sama seperti yang ditunjukkan oleh OP ...Jawaban:
Kritik Anda sepenuhnya dibenarkan.
Saya membahas masalah ini secara rinci di sini:
Menutup variabel loop dianggap berbahaya
Yang terakhir. Spesifikasi C # 1.0 sebenarnya tidak mengatakan apakah variabel loop berada di dalam atau di luar tubuh loop, karena tidak membuat perbedaan yang dapat diamati. Ketika semantik penutupan diperkenalkan di C # 2.0, pilihan dibuat untuk meletakkan variabel loop di luar loop, konsisten dengan loop "for".
Saya pikir itu adil untuk mengatakan bahwa semua menyesali keputusan itu. Ini adalah salah satu "gotcha" terburuk di C #, dan kami akan mengambil perubahan untuk memperbaikinya. Dalam C # 5 variabel loop foreach akan secara logis di dalam tubuh loop, dan karena itu penutupan akan mendapatkan salinan baru setiap kali.
The
for
Loop tidak akan berubah, dan perubahan tidak akan "kembali porting" dengan versi sebelumnya dari C #. Karena itu Anda harus terus berhati-hati saat menggunakan idiom ini.sumber
foreach
'aman' tapifor
tidak.Apa yang Anda tanyakan sepenuhnya dibahas oleh Eric Lippert dalam posting blognya. Menutup variabel loop yang dianggap berbahaya dan sekuelnya.
Bagi saya, argumen yang paling meyakinkan adalah bahwa memiliki variabel baru di setiap iterasi tidak akan konsisten dengan
for(;;)
loop gaya. Apakah Anda berharap memiliki yang baruint i
di setiap iterasifor (int i = 0; i < 10; i++)
?Masalah yang paling umum dengan perilaku ini adalah membuat penutupan atas variabel iterasi dan memiliki solusi yang mudah:
Posting blog saya tentang masalah ini: Penutupan atas variabel foreach dalam C # .
sumber
ref
.cut
untuk referensi / vars dancute
untuk menjaga nilai yang dievaluasi dalam penutupan yang dievaluasi sebagian).Setelah digigit oleh ini, saya memiliki kebiasaan memasukkan variabel yang didefinisikan secara lokal dalam lingkup terdalam yang saya gunakan untuk mentransfer ke penutupan apa pun. Dalam contoh Anda:
Saya lakukan:
Setelah Anda memiliki kebiasaan itu, Anda dapat menghindarinya dalam kasus yang sangat jarang Anda maksudkan untuk mengikat ke luar lingkup. Sejujurnya, saya tidak berpikir saya pernah melakukannya.
sumber
Di C # 5.0, masalah ini diperbaiki dan Anda bisa menutup variabel loop dan mendapatkan hasil yang Anda harapkan.
Spesifikasi bahasa mengatakan:
sumber