2008-11-07 9 views
154

में एक लूप में कैप्चर वैरिएबल मैं सी # के बारे में एक दिलचस्प मुद्दा से मुलाकात की। मेरे पास कोड जैसा है।सी #

List<Func<int>> actions = new List<Func<int>>(); 

int variable = 0; 
while (variable < 5) 
{ 
    actions.Add(() => variable * 2); 
    ++ variable; 
} 

foreach (var act in actions) 
{ 
    Console.WriteLine(act.Invoke()); 
} 

मैं उत्पादन 0, 2, 4, 6, 8 यह उम्मीद हालांकि, यह वास्तव में पांच 10s आउटपुट।

ऐसा लगता है कि यह सभी कार्यों के कारण एक कब्जे वाले चर का जिक्र है। नतीजतन, जब वे आते हैं, तो वे सभी एक ही आउटपुट होते हैं।

क्या प्रत्येक एक्शन इंस्टेंस के अपने कब्जे वाले चर के पास इस सीमा के चारों ओर काम करने का कोई तरीका है?

+9

इस विषय पर एरिक लिपर्ट की ब्लॉग श्रृंखला भी देखें: [लूप वेरिएबल कंसिडेड हार्मफुल पर बंद होना] (http://blogs.msdn.com/b/ericlippert/archive/tags/closures/) – Brian

+6

इसके अलावा, वे बदल रहे हैं सी # 5 काम करने के लिए जैसा कि आप एक foreach के भीतर उम्मीद करते हैं। (ब्रेकिंग चेंज) –

+0

संबंधित: [क्यों-यह-खराब-उपयोग-एक-पुनरावृत्ति-चर-इन-ए-लैम्ब्डा-अभिव्यक्ति] (http://stackoverflow.com/questions/227820/why-is -it-bad-to-use-an-iteration-variable-in-a-lambda-expression) – nawfal

उत्तर

137

हाँ - पाश अंदर चर की एक प्रति ले:

while (variable < 5) 
{ 
    int copy = variable; 
    actions.Add(() => copy * 2); 
    ++ variable; 
} 

आप के रूप में यदि सी # संकलक एक 'नई' स्थानीय चर हर बार यह चर घोषणा हिट बनाता है इसके बारे में सोच सकते हैं। वास्तव में यह उचित नई बंद वस्तुओं को बनाएगा, और यह जटिल हो जाता है (कार्यान्वयन के मामले में) यदि आप एकाधिक क्षेत्रों में चर का संदर्भ देते हैं, लेकिन यह काम करता है :)

ध्यान दें कि इस समस्या का एक और आम घटना उपयोग कर रहा है for या foreach:

for (int i=0; i < 10; i++) // Just one variable 
foreach (string x in foo) // And again, despite how it reads out loud 

अनुभाग इस के अधिक जानकारी के लिए सी # 3.0 कल्पना की 7.14.4.2 देखें, और मेरे article on closures और उदाहरण भी है।

+19

जॉन की पुस्तक पर भी एक बहुत अच्छा अध्याय है (विनम्र होना बंद करो, जॉन!) –

+21

यह बेहतर दिखता है कि अगर मैं इसे अन्य लोगों को प्लग करने देता हूं;) (मैं कबूल करता हूं कि मैं इसे उत्तर देने के उत्तर देने का प्रयास करता हूं।) –

+2

कभी, [email protected] पर प्रतिक्रिया की सराहना की जाएगी :) –

4

हाँ आप पाश के भीतर गुंजाइश variable करने की जरूरत है और लैम्ब्डा के लिए इसे उस तरह से पारित:

List<Func<int>> actions = new List<Func<int>>(); 

int variable = 0; 
while (variable < 5) 
{ 
    int variable1 = variable; 
    actions.Add(() => variable1 * 2); 
    ++variable; 
} 

foreach (var act in actions) 
{ 
    Console.WriteLine(act.Invoke()); 
} 

Console.ReadLine(); 
7

इस के चारों ओर जिस तरह से मूल्य आप एक प्रॉक्सी चर में की जरूरत है, और है स्टोर करने के लिए है कि चर प्राप्त पकड़े।

आईई।

while(variable < 5) 
{ 
    int copy = variable; 
    actions.Add(() => copy * 2); 
    ++variable; 
} 
+0

हाँ, यह काम करता है। पर क्यों? –

+0

मेरे संपादित उत्तर में स्पष्टीकरण देखें। मुझे अब spec का प्रासंगिक बिट मिल रहा है। –

+0

हाहा जोन, मैं वास्तव में सिर्फ आपके लेख को पढ़ता हूं: http://csharpindepth.com/Articles/Chapter5/Closures.aspx आप मेरे दोस्त को अच्छा काम करते हैं। – tjlevine

16

मेरा मानना ​​है कि क्या आप अनुभव कर रहे बंद http://en.wikipedia.org/wiki/Closure_(computer_science) के रूप में जाना कुछ है। आपके लैम्बा में एक वेरिएबल का संदर्भ है जो फ़ंक्शन के बाहर ही स्कॉप्ड होता है। जब तक आप इसे आमंत्रित नहीं करते हैं तब तक आपका लम्बा व्याख्या नहीं किया जाता है और एक बार यह वैरिएबल को निष्पादन समय पर मान प्राप्त होगा। ।

2

एक ही स्थिति बहु सूत्रण (सी # में हो रहा है, .NET 4.0]

निम्नलिखित कोड देखें:।

उद्देश्य क्रम में 1,2,3,4,5 मुद्रित करने के लिए है

for (int counter = 1; counter <= 5; counter++) 
{ 
    new Thread (() => Console.Write (counter)).Start(); 
} 

उत्पादन दिलचस्प है! (यह 21334 की तरह हो सकता है ...)

एकमात्र समाधान स्थानीय चर का प्रयोग है।

for (int counter = 1; counter <= 5; counter++) 
{ 
    int localVar= counter; 
    new Thread (() => Console.Write (localVar)).Start(); 
} 
+0

यह मेरी मदद नहीं करता है। अभी भी गैर निर्धारक। –

8

दृश्यों के पीछे, संकलक एक वर्ग उत्पन्न कर रहा है जो आपके विधि कॉल के बंद होने का प्रतिनिधित्व करता है। यह लूप के प्रत्येक पुनरावृत्ति के लिए क्लोजर क्लास के उस एकल उदाहरण का उपयोग करता है।कोड कुछ इस तरह है, जो यह देखना आसान क्यों बग होता है बनाता है दिखता है:

void Main() 
{ 
    List<Func<int>> actions = new List<Func<int>>(); 

    int variable = 0; 

    var closure = new CompilerGeneratedClosure(); 

    Func<int> anonymousMethodAction = null; 

    while (closure.variable < 5) 
    { 
     if(anonymousMethodAction == null) 
      anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod); 

     //we're re-adding the same function 
     actions.Add(anonymousMethodAction); 

     ++closure.variable; 
    } 

    foreach (var act in actions) 
    { 
     Console.WriteLine(act.Invoke()); 
    } 
} 

class CompilerGeneratedClosure 
{ 
    public int variable; 

    public int YourAnonymousMethod() 
    { 
     return this.variable * 2; 
    } 
} 

यह वास्तव में अपने नमूना से संकलित कोड नहीं है, लेकिन मैं अपने खुद के कोड की जांच की है और यह बहुत ज्यादा लग रहा है वास्तव में संकलक वास्तव में क्या उत्पन्न करेगा।