TL; DR - bunlar IL katmanında eşdeğer örneklerdir.
DotNetFiddle , ortaya çıkan IL'yi görmenize izin verdiği için bunu cevaplamayı güzelleştirir .
Testimi daha hızlı hale getirmek için döngü yapınızın biraz farklı bir varyasyonunu kullandım. Kullandım:
Varyasyon 1:
using System;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
int x;
int i;
for(x=0; x<=2; x++)
{
i = x;
Console.WriteLine(i);
}
}
}
Varyasyon 2:
Console.WriteLine("Hello World");
int x;
for(x=0; x<=2; x++)
{
int i = x;
Console.WriteLine(i);
}
Her iki durumda da, derlenmiş IL çıktısı aynı hale geldi.
.class public auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
.method public hidebysig static void Main() cil managed
{
//
.maxstack 2
.locals init (int32 V_0,
int32 V_1,
bool V_2)
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ldc.i4.0
IL_000d: stloc.0
IL_000e: br.s IL_001f
IL_0010: nop
IL_0011: ldloc.0
IL_0012: stloc.1
IL_0013: ldloc.1
IL_0014: call void [mscorlib]System.Console::WriteLine(int32)
IL_0019: nop
IL_001a: nop
IL_001b: ldloc.0
IL_001c: ldc.i4.1
IL_001d: add
IL_001e: stloc.0
IL_001f: ldloc.0
IL_0020: ldc.i4.2
IL_0021: cgt
IL_0023: ldc.i4.0
IL_0024: ceq
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: brtrue.s IL_0010
IL_002a: ret
} // end of method Program::Main
Sorunuzu cevaplamak için: derleyici değişkenin bildirimini optimize eder ve iki varyasyonu eşdeğer hale getirir.
Anladığım kadarıyla, .NET IL derleyicisi tüm değişken bildirimleri işlevin başlangıcına taşır, ancak açıkça belirttiğim iyi bir kaynak bulamadım 2 . Bu belirli örnekte, bu ifadeyle onları yukarı taşıdığını görüyorsunuz:
.locals init (int32 V_0,
int32 V_1,
bool V_2)
Burada karşılaştırma yaparken biraz takıntılı oluruz ....
Durum A, tüm değişkenler yukarı taşınır mı?
Bunu biraz daha ayrıntılı incelemek için aşağıdaki işlevi test ettim:
public static void Main()
{
Console.WriteLine("Hello World");
int x=5;
if (x % 2==0)
{
int i = x;
Console.WriteLine(i);
}
else
{
string j = x.ToString();
Console.WriteLine(j);
}
}
Buradaki fark , karşılaştırmaya dayanarak bir int i
veya bir beyan string j
etmemizdir. Derleyici yine tüm yerel değişkenleri fonksiyon 2'nin üstüne taşır :
.locals init (int32 V_0,
int32 V_1,
string V_2,
bool V_3)
int i
Bu örnekte bildirilmeyecek olsa da, bunu desteklemek için kodun hala oluşturulduğunu belirtmek ilginç buldum .
Durum B: Peki ya foreach
yerine for
?
Bundan foreach
farklı davranışları olduğu for
ve sorulanla aynı şeyi kontrol etmediğime dikkat çekildi. Sonuçta ortaya çıkan IL karşılaştırmak için bu iki kod bölümünü koydum.
int
döngü dışında bildirim:
Console.WriteLine("Hello World");
List<int> things = new List<int>(){1, 2, 3, 4, 5};
int i;
foreach(var thing in things)
{
i = thing;
Console.WriteLine(i);
}
int
Döngü içindeki bildirim:
Console.WriteLine("Hello World");
List<int> things = new List<int>(){1, 2, 3, 4, 5};
foreach(var thing in things)
{
int i = thing;
Console.WriteLine(i);
}
İlme ile elde edilen IL, foreach
ilmeğin kullanarak üretilen IL'den gerçekten farklıydı for
. Özellikle, init bloğu ve döngü bölümü değişti.
.locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0,
int32 V_1,
int32 V_2,
class [mscorlib]System.Collections.Generic.List`1<int32> V_3,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> V_4,
bool V_5)
...
.try
{
IL_0045: br.s IL_005a
IL_0047: ldloca.s V_4
IL_0049: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_004e: stloc.1
IL_004f: nop
IL_0050: ldloc.1
IL_0051: stloc.2
IL_0052: ldloc.2
IL_0053: call void [mscorlib]System.Console::WriteLine(int32)
IL_0058: nop
IL_0059: nop
IL_005a: ldloca.s V_4
IL_005c: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0061: stloc.s V_5
IL_0063: ldloc.s V_5
IL_0065: brtrue.s IL_0047
IL_0067: leave.s IL_0078
} // end .try
finally
{
IL_0069: ldloca.s V_4
IL_006b: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0076: nop
IL_0077: endfinally
} // end handler
foreach
Yaklaşım daha yerel değişkenler oluşturulur ve bazı ek dallanma gereklidir. Esasen, ilk kez sayımın ilk yinelemesini elde etmek için döngünün sonuna atlar ve daha sonra döngü kodunu yürütmek için döngünün neredeyse üstüne geri atlar. Daha sonra beklediğiniz gibi dönmeye devam eder.
Ancak for
ve foreach
yapılarının kullanılmasının neden olduğu dallanma farklılıklarının ötesinde, bildirimin nereye yerleştirildiğine bağlı olarak IL'de fark yoktuint i
. Yani hala iki yaklaşımın eşdeğeriyiz.
Durum C: Farklı derleyici sürümleri ne olacak?
1'de bırakılan bir yorumda, foreach ile değişken erişim ve kapatma kullanma hakkında bir uyarı ile ilgili bir SO sorusuna bir bağlantı vardı . Bu soruda gerçekten dikkatimi çeken kısım, .NET 4.5 derleyicisinin derleyicinin önceki sürümlerine göre çalışma biçiminde farklılıklar olabileceğiydi.
İşte DotNetFiddler sitesi beni hayal kırıklığına uğrattı - mevcut olan tek şey .NET 4.5 ve Roslyn derleyicisinin bir versiyonuydu. Bu yüzden Visual Studio'nun yerel bir örneğini getirdim ve kodu test etmeye başladım. Aynı şeyleri karşılaştırmak emin olmak için .NET 4.5 yerel olarak oluşturulan kodu DotNetFiddler kodu ile karşılaştırdık.
Belirttiğim tek fark, yerel init bloğu ve değişken bildirimi ile oldu. Yerel derleyici değişkenlerin isimlendirilmesinde biraz daha spesifikti.
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> things,
[1] int32 thing,
[2] int32 i,
[3] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal0',
[4] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> CS$5$0000,
[5] bool CS$4$0001)
Ama bu küçük farkla, şimdiye kadar çok iyiydi. DotNetFiddler derleyicisi ile yerel VS örneğimin ürettiği şey arasında eşdeğer IL çıkışı vardı.
Daha sonra .NET 4, .NET 3.5 ve iyi ölçmek için .NET 3.5 Release modu hedefleyen proje yeniden.
Ve bu ek vakaların üçünde de üretilen IL eşdeğerdi. Hedeflenen .NET sürümünün, bu örneklerde oluşturulan IL üzerinde hiçbir etkisi yoktur.
Bu macerayı özetlemek gerekirse: Derleyicinin ilkel türü nerede bildirdiğinizi umursamadığını ve her iki bildirim yöntemiyle bellek veya performans üzerinde hiçbir etkisi olmadığını güvenle söyleyebiliriz. Ve bu, a for
veya foreach
loop kullanmasına bakılmaksızın geçerlidir .
foreach
Döngünün içine bir kapanış içeren başka bir vaka çalıştırmayı düşündüm . Fakat ilkel tip değişkeninin nerede bildirildiğini sormuştunuz, bu yüzden sormak istediğiniz şeyin çok ötesine geçtiğimi düşündüm. Daha önce bahsettiğim SO sorusunun, her bir yineleme değişkeni üzerindeki kapatma etkileri hakkında iyi bir genel bakış sağlayan harika bir cevabı var.
1 Andy'ye, foreach
döngülerdeki kapanışları ele alan SO sorusuna orijinal bağlantıyı sağladığı için teşekkür ederiz .
2 ECMA-335 spesifikasyonunun buna I.12.3.2.2 'Yerel değişkenler ve argümanlar' bölümü ile hitap ettiğini belirtmek gerekir . Ortaya çıkan IL'yi görmek ve daha sonra neler olup bittiğine dair net olması için bölümü okumak zorunda kaldım. Bunu sohbete işaret eden mandal ucube sayesinde.