Apakah ada perbedaan antara kedua versi kode ini?
foreach (var thing in things)
{
int i = thing.number;
// code using 'i'
// pay no attention to the uselessness of 'i'
}
int i;
foreach (var thing in things)
{
i = thing.number;
// code using 'i'
}
Atau apakah kompiler tidak peduli? Ketika saya berbicara tentang perbedaan, saya maksudkan dalam hal kinerja dan penggunaan memori. ..Atau pada dasarnya hanya ada perbedaan atau apakah keduanya akhirnya menjadi kode yang sama setelah kompilasi?
c#
performance
memory
Alternatex
sumber
sumber
Jawaban:
TL; DR - mereka adalah contoh yang setara di lapisan IL.
DotNetFiddle membuat ini cukup untuk menjawab karena memungkinkan Anda untuk melihat IL yang dihasilkan.
Saya menggunakan variasi yang sedikit berbeda dari konstruksi loop Anda untuk membuat pengujian saya lebih cepat. Saya menggunakan:
Variasi 1:
Variasi 2:
Dalam kedua kasus, output IL yang dikompilasi menghasilkan hal yang sama.
Jadi untuk menjawab pertanyaan Anda: kompiler mengoptimalkan deklarasi variabel, dan membuat dua variasi yang setara.
Untuk pemahaman saya, .NET IL compiler memindahkan semua deklarasi variabel ke awal fungsi tetapi saya tidak dapat menemukan sumber yang baik yang dengan jelas menyatakan bahwa 2 . Dalam contoh khusus ini, Anda melihat bahwa itu menggerakkan mereka dengan pernyataan ini:
Di mana kita menjadi agak terlalu obsesif dalam membuat perbandingan ....
Kasus A, apakah semua variabel dipindahkan ke atas?
Untuk menggali sedikit lebih jauh, saya menguji fungsi berikut:
Perbedaannya di sini adalah bahwa kami menyatakan salah
int i
ataustring j
berdasarkan perbandingan. Sekali lagi, kompilator memindahkan semua variabel lokal ke atas fungsi 2 dengan:Saya merasa menarik untuk dicatat bahwa meskipun
int i
tidak akan dideklarasikan dalam contoh ini, kode untuk mendukungnya masih dihasilkan.Kasus B: Bagaimana kalau
foreach
bukanfor
?Itu menunjukkan bahwa
foreach
memiliki perilaku yang berbeda darifor
dan bahwa saya tidak memeriksa hal yang sama yang telah ditanyakan. Jadi saya memasukkan dua bagian kode ini untuk membandingkan IL yang dihasilkan.int
deklarasi di luar loop:int
deklarasi di dalam loop:IL yang dihasilkan dengan
foreach
loop itu memang berbeda dari IL yang dihasilkan menggunakanfor
loop. Secara khusus, blok init dan bagian loop berubah.The
foreach
Pendekatan yang dihasilkan lebih variabel lokal dan diperlukan beberapa percabangan tambahan. Pada dasarnya, pada saat pertama di dalamnya melompat ke ujung loop untuk mendapatkan iterasi pertama enumerasi dan kemudian melompat kembali ke hampir bagian atas loop untuk menjalankan kode loop. Kemudian terus berlanjut seperti yang Anda harapkan.Tetapi di luar perbedaan percabangan yang disebabkan oleh penggunaan
for
danforeach
konstruksi, tidak ada perbedaan dalam IL berdasarkan di manaint i
deklarasi ditempatkan. Jadi kita masih pada dua pendekatan yang setara.Kasus C: Bagaimana dengan versi kompiler yang berbeda?
Dalam komentar yang tersisa 1 , ada tautan ke pertanyaan SO tentang peringatan tentang akses variabel dengan foreach dan menggunakan penutupan . Bagian yang benar-benar menarik perhatian saya dalam pertanyaan itu adalah bahwa mungkin ada perbedaan dalam cara kerja kompiler .NET 4.5 dibandingkan versi sebelumnya dari kompiler.
Dan di situlah situs DotNetFiddler mengecewakan saya - yang mereka miliki hanyalah .NET 4.5 dan versi dari kompiler Roslyn. Jadi saya membuka contoh lokal Visual Studio dan mulai menguji kode. Untuk memastikan saya membandingkan hal yang sama, saya membandingkan kode yang dibuat secara lokal di .NET 4.5 dengan kode DotNetFiddler.
Satu-satunya perbedaan yang saya perhatikan adalah dengan blok init lokal dan deklarasi variabel. Kompiler lokal sedikit lebih spesifik dalam penamaan variabel.
Tetapi dengan perbedaan kecil itu, sejauh ini, sangat bagus. Saya memiliki output IL yang setara antara kompiler DotNetFiddler dan apa yang diproduksi oleh instance VS lokal saya.
Jadi saya kemudian membangun kembali penargetan proyek .NET 4, .NET 3.5, dan untuk ukuran yang baik. NET 3.5 Rilis mode.
Dan dalam ketiga kasus tambahan tersebut, IL yang dihasilkan setara. Versi .NET yang ditargetkan tidak berpengaruh pada IL yang dihasilkan dalam sampel ini.
Untuk merangkum petualangan ini: Saya pikir kita dapat dengan yakin mengatakan bahwa kompiler tidak peduli di mana Anda mendeklarasikan tipe primitif dan bahwa tidak ada efek pada memori atau kinerja dengan metode deklarasi. Dan itu berlaku terlepas dari menggunakan a
for
atauforeach
loop.Saya mempertimbangkan menjalankan kasus lain yang memasukkan penutupan di dalam
foreach
loop. Tetapi Anda telah bertanya tentang efek di mana variabel tipe primitif dideklarasikan, jadi saya pikir saya menggali terlalu jauh melampaui apa yang Anda minati. Pertanyaan SO yang saya sebutkan sebelumnya memiliki jawaban yang bagus yang memberikan tinjauan yang baik tentang efek penutupan pada variabel iteach masing-masing.1 Terima kasih kepada Andy karena telah memberikan tautan asli ke pertanyaan SO yang membahas penutupan dalam
foreach
loop.2 Perlu dicatat bahwa spesifikasi ECMA-335 membahas hal ini dengan bagian I.12.3.2.2 'Variabel dan argumen lokal'. Saya harus melihat IL yang dihasilkan dan kemudian membaca bagian untuk menjadi jelas tentang apa yang sedang terjadi. Terima kasih kepada ratchet freak karena menunjukkan hal itu dalam obrolan.
sumber
foreach
loop dan juga memeriksa versi .NET yang ditargetkan.Bergantung pada kompiler apa yang Anda gunakan (Saya bahkan tidak tahu apakah C # memiliki lebih dari satu), kode Anda akan dioptimalkan sebelum diubah menjadi program. Kompiler yang baik akan melihat bahwa Anda menginisialisasi ulang variabel yang sama setiap kali dengan nilai yang berbeda dan mengelola ruang memori untuknya secara efisien.
Jika Anda menginisialisasi variabel yang sama ke konstanta setiap kali, kompiler juga akan menginisialisasinya sebelum loop dan referensi itu.
Itu semua tergantung seberapa baik kompiler Anda ditulis, tetapi sejauh standar pengkodean yang bersangkutan variabel harus selalu memiliki ruang lingkup sesedikit mungkin . Jadi menyatakan di dalam lingkaran adalah apa yang saya selalu diajarkan.
sumber
pada awalnya Anda hanya mendeklarasikan dan menginisialisasi di dalam loop sehingga setiap kali loop itu akan diinisialisasi ulang "i" di dalam loop. Di detik Anda hanya mendeklarasikan di luar loop.
sumber