सूची <T> .ForEach अपनी सूची को संशोधित करने की अनुमति क्यों देता है?


90

अगर मैं उपयोग करता हूं:

var strings = new List<string> { "sample" };
foreach (string s in strings)
{
  Console.WriteLine(s);
  strings.Add(s + "!");
}

Addमें foreachएक InvalidOperationException फेंकता (संग्रह संशोधित किया गया था; गणन किए गए कार्य पूरे नहीं हो सकता है), जो मैं तार्किक विचार करते हैं, के बाद से हम अपने पैरों के नीचे से गलीचा खींच रहे हैं।

हालांकि, अगर मैं उपयोग करता हूं:

var strings = new List<string> { "sample" };
strings.ForEach(s =>
  {
    Console.WriteLine(s);
    strings.Add(s + "!");
  });

जब तक यह एक OutOfMemoryException फेंकता नहीं है, यह तुरंत पैर में खुद को गोली मारता है।

यह मेरे लिए एक आश्चर्य के रूप में आता है, जैसा कि मैंने हमेशा सोचा था कि List.ForEach या तो सिर्फ एक आवरण था या foreachउसके लिए for
क्या किसी के पास इस व्यवहार के कैसे और क्यों के लिए स्पष्टीकरण है?

( सामान्य सूची के लिए ForEach लूप द्वारा अंत में दोहराया गया )


7
मैं सहमत हूँ। यह है - संदिग्ध। मैं आपसे कहता हूं कि आप Microsoft से कनेक्ट करें और स्पष्टीकरण मांगें।
टॉमटॉम

4
"यह मेरे लिए एक आश्चर्य के रूप में आता है, जैसा कि मैंने हमेशा सोचा था कि List.ForEach या तो सिर्फ एक आवरण के लिए foreachया उसके लिए था for।" यह अभी भी उपयोग कर सकता है for। आप एक forलूप में एक ही क्रिया कर सकते हैं और परिणाम के रूप में एक ही OutOfMemoryException उत्पन्न कर सकते हैं ।
एंथनी Pegram

यह मेरे सवाल पर आधारित है: stackoverflow.com/q/9311272/132239 , शुक्रिया SWeko यह विवरण में प्राप्त करने के लिए
Kasrak

जवाबों:


68

ऐसा इसलिए है क्योंकि ForEachविधि एन्यूमरेटर का उपयोग नहीं करता है, यह एक forलूप के साथ आइटम के माध्यम से लूप करता है:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

(JustDecompile के साथ प्राप्त कोड)

चूंकि एन्यूमरेटर का उपयोग नहीं किया जाता है, यह कभी नहीं जांचता है कि क्या सूची बदल गई है, और forलूप की अंतिम स्थिति कभी नहीं पहुंचती है क्योंकि _sizeहर पुनरावृत्ति में वृद्धि हुई है।


हाँ, लेकिन _sizeगणना कैसे की जाती है? यदि यह सिर्फ पूर्व-गणना है तो यदि मेरे उदाहरण के लिए बस एक बार चलना चाहिए। यह स्पष्ट रूप से किसी भी तरह ताज़ा है।
SWeko

7
यह ऐड मेथड में रिफ्रेश होता है -> यह._इटीम्स [यह._साइज़ ++] = आइटम;
Fabio

1
@Seko, इसकी गणना नहीं की जाती है, यह हर बार अपडेट किया जाता है कि कोई आइटम जोड़ा या हटाया गया है।
थॉमस लेवेस्क

1
इसमें एक _versionनिजी चर है List<T>जो इस प्रकार के परिदृश्यों का पता लगा सकता है, क्योंकि यह उन ऑपरेशनों पर अपडेट किया जाता है जो सूची को स्वयं बदलते हैं।
13

आप पहले आकार प्राप्त करके अपवादों से बच सकते हैं (intSize = this._size), फिर लूप के लिए इसका उपयोग कर रहे हैं?
लेज़लो

14

List<T>.ForEachforअंदर से कार्यान्वित किया जाता है, इसलिए यह एन्यूमरेटर का उपयोग नहीं करता है और यह संग्रह को संशोधित करने की अनुमति देता है।


6

क्योंकि सूची वर्ग में संलग्न ForEach आंतरिक रूप से लूप के लिए उपयोग करता है जो सीधे इसके आंतरिक सदस्यों से जुड़ा होता है - जिसे आप .NET फ्रेमवर्क के लिए स्रोत कोड डाउनलोड करके देख सकते हैं।

http://referencesource.microsoft.com/netframework.aspx

जहां एक फॉरेस्ट लूप पहले है और एक कंपाइलर ऑप्टिमाइज़ेशन है, लेकिन एक ऑब्जर्वर के रूप में संग्रह के खिलाफ भी काम करना चाहिए - इसलिए यदि संग्रह को संशोधित किया जाता है तो यह एक अपवाद फेंकता है।


और @Thomas पोस्ट पर टिप्पणी का जवाब देने के लिए कि यह कैसे ताज़ा होती है - आंतरिक सदस्यों को ताज़ा किया जाता है जब ऐड कहा जाता है, इसलिए यह परिवर्तनों के साथ बनाए रखने में सक्षम है। यदि आप एक प्रविष्टि करने के लिए थे, तो एक से कम एक इंडेक्स में, आप उस आइटम पर कभी भी काम नहीं करेंगे क्योंकि यह पहले ही उस आइटम को पुनरावृत्त कर चुका है। लेकिन जब से आप अंत में जोड़ रहे हैं यह काम करता है।
माइक पेरेनॉड

1
हां, सिर्फ 'सैंपल' को प्रिंट करने के Addसाथ लाइन को बदलना strings.Insert(0, s + "!")। यह अजीब बात है कि प्रलेखन में इसका उल्लेख बिल्कुल नहीं है।
SWeko

ठीक है, मुझे लगता है कि माइक्रोसॉफ्ट ने महसूस किया कि उनके दस्तावेज़ में मौजूद प्रत्येक कैविएट प्रदान करना असंभव है - इसलिए वे अब अपना स्रोत कोड प्रदान करते हैं। मुझे लगता है कि एक बेहतर समाधान ईमानदारी से मिल रहा है लेकिन एकमात्र समस्या मुझे मिली है कि WF जैसे उत्पाद जल्दी से अपडेट नहीं होते हैं - 4.x WF स्रोत कोड उपलब्ध नहीं है।
माइक पेरेनॉड

4

हम इस मुद्दे के बारे में जानते हैं, जब यह मूल रूप से लिखा गया था तो यह एक निरीक्षण था। दुर्भाग्य से, हम इसे बदल नहीं सकते क्योंकि यह अब इस पहले से काम कर रहे कोड को चलने से रोकेगा:

        var list = new List<string>();
        list.Add("Foo");
        list.Add("Bar");

        list.ForEach((item) => 
        { 
            if(item=="Foo") 
                list.Remove(item); 
        });

इस पद्धति की उपयोगिता ही संदिग्ध है क्योंकि एरिक लिपर्ट ने बताया है, इसलिए हमने इसे .NET के लिए मेट्रो स्टाइल ऐप (यानी विंडोज 8 ऐप) के लिए शामिल नहीं किया।

डेविड कीन (बीसीएल टीम)


1
मैं देखता हूं कि यह एक बड़ा बुरा बदलाव होगा, लेकिन फिर भी यह गैर-स्पष्ट तरीकों से विफल हो सकता है, और यह कभी भी अच्छी बात नहीं है। मैं एक परिदृश्य नहीं देख सकता, जहाँ ForEach पद्धति का उपयोग करना सरल है (या foreach के लिए बेहतर है यदि मूल सूची की आवश्यकता नहीं है)
SWeko
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.