TL; DR - เป็นตัวอย่างที่เท่าเทียมกันที่เลเยอร์ IL
DotNetFiddleทำให้คำตอบนี้สวยเพราะช่วยให้คุณเห็นผลลัพท์ของ IL
ฉันใช้รูปแบบวนรอบที่แตกต่างกันเล็กน้อยเพื่อให้การทดสอบของฉันเร็วขึ้น ฉันใช้:
รูปแบบที่ 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);
}
}
}
รูปแบบที่ 2:
Console.WriteLine("Hello World");
int x;
for(x=0; x<=2; x++)
{
int i = x;
Console.WriteLine(i);
}
ในทั้งสองกรณีเอาต์พุต IL ที่รวบรวมจะแสดงผลเหมือนกัน
.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
ดังนั้นเพื่อตอบคำถามของคุณ: คอมไพเลอร์จะปรับการประกาศตัวแปรให้เหมาะสมที่สุดและทำให้ทั้งสองรูปแบบเทียบเท่ากัน
ความเข้าใจของคอมไพเลอร์ .NET IL ย้ายประกาศตัวแปรทั้งหมดไปยังจุดเริ่มต้นของการทำงาน แต่ฉันไม่สามารถหาแหล่งที่ดีที่ระบุไว้ชัดเจนว่า2 ในตัวอย่างนี้คุณเห็นว่ามันทำให้พวกเขาขึ้นกับคำสั่งนี้:
.locals init (int32 V_0,
int32 V_1,
bool V_2)
ประเด็นที่เราได้รับการครอบงำมากเกินไปในการเปรียบเทียบ ....
กรณี A ตัวแปรทั้งหมดถูกเลื่อนขึ้นหรือไม่?
ในการขุดลงไปอีกเล็กน้อยฉันได้ทดสอบฟังก์ชั่นต่อไปนี้:
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);
}
}
ความแตกต่างที่นี่ก็คือเราประกาศอย่างใดอย่างหนึ่งint i
หรือstring j
ตามการเปรียบเทียบ อีกครั้งคอมไพเลอร์ย้ายตัวแปรท้องถิ่นทั้งหมดไปยังด้านบนของฟังก์ชัน2ด้วย:
.locals init (int32 V_0,
int32 V_1,
string V_2,
bool V_3)
ฉันพบว่าน่าสนใจที่จะทราบว่าแม้ว่าint i
จะไม่ได้รับการประกาศในตัวอย่างนี้ แต่รหัสที่ให้การสนับสนุนนั้นยังคงถูกสร้างขึ้น
กรณี B: แล้วforeach
แทนที่จะเป็นfor
อะไรล่ะ
มันชี้ให้เห็นว่าforeach
มีพฤติกรรมที่แตกต่างกว่าfor
และฉันไม่ได้ตรวจสอบสิ่งเดียวกับที่ถูกถามเกี่ยวกับ ดังนั้นฉันจึงใส่โค้ดสองส่วนนี้เพื่อเปรียบเทียบ IL ที่ได้
int
ประกาศนอกวง:
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
ประกาศภายในของวง:
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);
}
ผลลัพท์ของ IL กับforeach
ลูปนั้นแตกต่างจาก IL ที่สร้างโดยใช้for
ลูป โดยเฉพาะบล็อก init และส่วนลูปจะเปลี่ยนไป
.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
วิธีการสร้างตัวแปรท้องถิ่นมากขึ้นและจำเป็นบางแขนงเพิ่มเติม โดยพื้นฐานแล้วในครั้งแรกที่มันกระโดดไปยังจุดสิ้นสุดของลูปเพื่อรับการวนซ้ำครั้งแรกของการแจงนับแล้วกระโดดกลับไปที่เกือบด้านบนของลูปเพื่อรันโค้ดลูป จากนั้นมันจะวนซ้ำตามที่คุณคาดไว้
แต่นอกเหนือจากความแตกต่างของการแตกกิ่งก้านที่เกิดจากการใช้for
และการforeach
สร้างไม่มีความแตกต่างใน IL ตามที่int i
วางประกาศไว้ ดังนั้นเรายังอยู่ที่สองวิธีที่เท่าเทียมกัน
กรณี C: แล้วคอมไพเลอร์เวอร์ชั่นต่าง ๆ ล่ะ?
ในความคิดเห็นที่เหลือ1มีการเชื่อมโยงไปอีกด้วยคำถาม SO เกี่ยวกับคำเตือนเกี่ยวกับการเข้าถึงตัวแปรกับ foreach และการใช้ปิด ส่วนที่จับตาของฉันจริงๆในคำถามนั้นก็คืออาจมีความแตกต่างในวิธีการที่คอมไพเลอร์. NET 4.5 ทำงานกับคอมไพเลอร์รุ่นก่อนหน้า
และนั่นคือสิ่งที่เว็บไซต์ DotNetFiddler ทำให้ฉันผิดหวัง - สิ่งที่พวกเขามีอยู่คือ. NET 4.5 และคอมไพเลอร์ Roslyn รุ่นหนึ่ง ดังนั้นฉันจึงนำอินสแตนซ์ในท้องถิ่นของ Visual Studio ขึ้นมาและเริ่มทดสอบโค้ด เพื่อให้แน่ใจว่าฉันกำลังเปรียบเทียบสิ่งเดียวกันฉันเปรียบเทียบโค้ดภายในเครื่องที่. NET 4.5 กับโค้ด DotNetFiddler
ข้อแตกต่างเดียวที่ฉันสังเกตเห็นคือกับการเริ่มต้นบล็อกท้องถิ่นและการประกาศตัวแปร คอมไพเลอร์ในพื้นที่มีความเฉพาะเจาะจงมากกว่านี้เล็กน้อยในการตั้งชื่อตัวแปร
.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)
แต่ด้วยความแตกต่างเล็กน้อยนั้นมันก็ยังดีอยู่ดี ฉันมีเอาต์พุต IL ที่เท่ากันระหว่างคอมไพเลอร์ DotNetFiddler และอินสแตนซ์ VS ในพื้นที่ของฉันกำลังสร้าง
ดังนั้นฉันจึงสร้างโครงการที่กำหนดเป้าหมาย. NET 4,. NET 3.5 ขึ้นมาใหม่และเพื่อทำการวัดที่ดีในโหมด NET 3.5 Release
และในทั้งสามกรณีเพิ่มเติมนั้น IL ที่ถูกสร้างขึ้นนั้นเทียบเท่ากัน เวอร์ชัน. NET เป้าหมายนั้นไม่มีผลกับ IL ที่สร้างขึ้นในตัวอย่างเหล่านี้
เพื่อสรุปการผจญภัยนี้: ฉันคิดว่าเราสามารถพูดได้อย่างมั่นใจว่าคอมไพเลอร์ไม่สนใจว่าคุณจะประกาศประเภทดั้งเดิมและไม่มีผลต่อหน่วยความจำหรือประสิทธิภาพด้วยวิธีการประกาศอย่างใดอย่างหนึ่ง และนั่นถือเป็นจริงโดยไม่คำนึงถึงการใช้for
หรือforeach
ลูป
ฉันคิดว่ากำลังวิ่งอีกกรณีที่รวมการปิดด้านในของforeach
ลูป แต่คุณได้ถามเกี่ยวกับผลกระทบของการประกาศตัวแปรประเภทดั้งเดิมดังนั้นฉันจึงคิดว่าฉันกำลังเจาะลึกเกินกว่าที่คุณสนใจถาม คำถาม SO ที่ฉันกล่าวถึงก่อนหน้านี้มีคำตอบที่ดีซึ่งให้ภาพรวมที่ดีเกี่ยวกับผลกระทบการปิดสำหรับตัวแปรการทำซ้ำแต่ละครั้ง
1 ขอบคุณ Andy ที่ให้ลิงก์ดั้งเดิมไปยังการปิดการตอบคำถาม SO ภายในforeach
ลูป
2 เป็นที่น่าสังเกตว่าข้อมูลจำเพาะของ ECMA-335กล่าวถึงเรื่องนี้ในหัวข้อ I.12.3.2.2 'ตัวแปรและการขัดแย้งในท้องถิ่น' ฉันต้องดู IL ที่เกิดขึ้นแล้วอ่านหัวข้อเพื่อให้ชัดเจนเกี่ยวกับสิ่งที่เกิดขึ้น ขอบคุณวงล้อประหลาดที่ชี้ว่าในการแชท