टीएल; डीआर - वे आईएल परत पर समान उदाहरण हैं।
DotNetFiddle यह जवाब देने के लिए बहुत सुंदर बनाता है क्योंकि यह आपको परिणामी आईएल को देखने की अनुमति देता है।
अपने परीक्षण को तेज करने के लिए मैंने आपके लूप निर्माण की थोड़ी भिन्नता का उपयोग किया। मैंनें इस्तेमाल किया:
विविधता 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)
जिसमें हमें तुलना करने में थोड़ा जुनूनी हो जाता है ...।
केस ए, क्या सभी चर बढ़ गए हैं?
इसे थोड़ा और खोदने के लिए, मैंने निम्नलिखित फ़ंक्शन का परीक्षण किया:
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
इस उदाहरण में घोषित नहीं किया जाएगा, फिर भी समर्थन करने के लिए कोड अभी भी उत्पन्न होता है।
केस बी: 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);
}
foreach
लूप के परिणामस्वरूप आईएल वास्तव में लूप का उपयोग करके उत्पन्न आईएल से अलग था for
। विशेष रूप से, इनिट ब्लॉक और लूप सेक्शन बदल गया।
.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
निर्माणों के उपयोग के कारण होने वाली शाखाओं के अंतर से परे , आईएल के आधार पर कोई अंतर नहीं था जहां int i
घोषणा की गई थी। इसलिए हम अभी भी दो दृष्टिकोणों के समतुल्य हैं।
केस सी: विभिन्न संकलक संस्करणों के बारे में क्या?
एक टिप्पणी जो 1 छोड़ दी गई थी , उसमें एसओ के लिए एक लिंक था जिसमें फ़ॉरच के साथ परिवर्तनीय पहुंच के बारे में चेतावनी दी गई थी और बंद करने का उपयोग किया गया था । उस प्रश्न में मेरी आंख को पकड़ने वाला हिस्सा यह था कि .NET 4.5 संकलक ने बनाम संकलक के पूर्व संस्करणों में काम करने के तरीके में अंतर हो सकता है।
और यहीं से DotNetFiddler साइट ने मुझे निराश किया - उनके पास जो भी उपलब्ध था वह था .NET 4.5 और रोसलिन कंपाइलर का एक संस्करण। इसलिए मैंने विज़ुअल स्टूडियो का एक स्थानीय उदाहरण पेश किया और कोड का परीक्षण शुरू किया। यह सुनिश्चित करने के लिए कि मैं समान चीजों की तुलना कर रहा था, मैंने .NET 4.5 पर स्थानीय रूप से निर्मित कोड की तुलना डॉटनेटफीडलर कोड से की।
एकमात्र अंतर जो मैंने नोट किया, वह स्थानीय इनिट ब्लॉक और वेरिएबल डिक्लेरेशन के साथ था। चरों के नामकरण में स्थानीय कंपाइलर थोड़ा अधिक विशिष्ट था।
.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)
लेकिन उस मामूली अंतर के साथ, यह इतना अच्छा था। मेरे पास DotNetFiddler संकलक के बीच समान आईएल आउटपुट था और मेरा स्थानीय वीएस उदाहरण क्या पैदा कर रहा था।
इसलिए मैंने फिर .NET 4, .NET 3.5, और अच्छे उपाय .NET 3.5 रिलीज मोड के लिए लक्ष्यीकरण परियोजना को फिर से बनाया।
और उन तीन अतिरिक्त मामलों में, उत्पन्न IL बराबर था। इन नमूनों में उत्पन्न IL पर लक्षित .NET संस्करण का कोई प्रभाव नहीं पड़ा।
इस साहसिक कार्य को संक्षेप में प्रस्तुत करने के लिए: मुझे लगता है कि हम आत्मविश्वास से कह सकते हैं कि संकलक को परवाह नहीं है कि आप आदिम प्रकार की घोषणा कहां करते हैं और यह कि घोषणा पद्धति के साथ स्मृति या प्रदर्शन पर कोई प्रभाव नहीं पड़ता है। और यह सच है कि एक for
या foreach
पाश का उपयोग करने की परवाह किए बिना ।
मैं अभी तक एक और मामले को चलाने पर विचार कर रहा था जिसमें foreach
लूप के अंदर एक क्लोजर शामिल था । लेकिन आपने उन प्रभावों के बारे में पूछा था जहां एक आदिम प्रकार चर घोषित किया गया था, इसलिए मुझे लगा कि आप के बारे में पूछने में जो रुचि थी, उससे बहुत अधिक मैं दूर था। पहले जिस एसओ प्रश्न का मैंने उल्लेख किया है, उसका एक शानदार उत्तर है जो कि फ़ॉरच्यू इटरेशन वेरिएबल पर क्लोज़र इफेक्ट के बारे में एक अच्छा अवलोकन प्रदान करता है।
1 छोरों के भीतर बंद होने वाले एसओ प्रश्न को मूल लिंक प्रदान करने के लिए एंडी को धन्यवाद foreach
।
2 यह ध्यान देने योग्य है कि ECMA-335 ने इसे I.12.3.2.2 खंड 'स्थानीय चर और तर्कों' के साथ संबोधित किया है। मुझे परिणामी IL को देखना था और फिर जो कुछ चल रहा था उसके बारे में स्पष्ट होने के लिए अनुभाग पढ़ें। चैट में यह इंगित करने के लिए शाफ़्ट फ्रीक का धन्यवाद।