क्यों इस कोड को 'संग्रह संशोधित किया गया था' फेंकता है, लेकिन जब मैं इसके पहले कुछ पुनरावृति करता हूं, तो यह नहीं होता है?


102
var ints = new List< int >( new[ ] {
    1,
    2,
    3,
    4,
    5
} );
var first = true;
foreach( var v in ints ) {
    if ( first ) {
        for ( long i = 0 ; i < int.MaxValue ; ++i ) { //<-- The thing I iterate
            ints.Add( 1 );
            ints.RemoveAt( ints.Count - 1 );
        }
        ints.Add( 6 );
        ints.Add( 7 );
    }
    Console.WriteLine( v );
    first = false;
}

यदि आप आंतरिक forलूप की टिप्पणी करते हैं , तो यह फेंकता है, यह स्पष्ट रूप से है क्योंकि हमने संग्रह में परिवर्तन किया था।

अब यदि आप इसे अनसुना करते हैं, तो यह लूप हमें उन दो वस्तुओं को जोड़ने की अनुमति क्यों देता है? इसे आधा मिनट (पेंटियम सीपीयू पर) की तरह चलाने में थोड़ी देर लगती है, लेकिन यह फेंकता नहीं है, और मजेदार बात यह है कि यह है:

छवि

यह थोड़ा अपेक्षित था, लेकिन यह इंगित करता है कि हम बदल सकते हैं और यह वास्तव में संग्रह को बदलता है। कोई भी विचार क्यों इस व्यवहार को प्रस्तुत कर रहा है?


2
यह तो दिलचस्प है। मैं व्यवहार को पुनः सकता है, लेकिन नहीं अगर मैं की तरह 100 एक मूल्य के Int.MaxValue से आंतरिक पाश बदल
स्टीव

आपने कब तक इंतजार किया? int.MaxValueपुनरावृत्तियों को पूरा करने में काफी समय लगता है ...
जॉन स्कीट

1
मेरा मानना ​​है कि यदि प्रत्येक लूप की शुरुआत में संग्रह को संशोधित किया गया है, तो यह देखने के लिए फ़ॉरचेक चेक .... इसलिए जोड़ना और फिर प्रत्येक लूप के भीतर आइटम को हटाने से कोई त्रुटि नहीं होती है।
काज

6
आप संदर्भ स्रोत को देखकर और परिवर्तन का पता लगाने के तरीके को देखकर इस प्रश्न का उत्तर देने में सक्षम हो सकते हैं । हर कोई जानता है कि संदर्भ स्रोत भी मौजूद नहीं है, बस शब्द फैल रहा है :)
क्रिस्टोफर Currens

2
बस जिज्ञासा से बाहर: क्या आपने इस मुद्दे को वास्तविक दुनिया के कोड में रखा था?
ken2k

जवाबों:


119

समस्या यह है कि List<T>संशोधनों का पता intलगाने का तरीका एक संस्करण फ़ील्ड रखकर , प्रत्येक संशोधन पर इसे बढ़ाना है। इसलिए, यदि आपने पुनरावृत्तियों के बीच सूची में कुछ 2 से 32 संशोधन किए हैं , तो यह उन संशोधनों को अदृश्य रूप में प्रस्तुत कर देगा जहां तक ​​पता लगाने का संबंध है। (यह से अतिप्रवाह जाएगा करने के लिए और अंत में अपनी प्रारंभिक मूल्य वापस करने के लिए मिलता है।)int.MaxValueint.MinValue

यदि आप अपने कोड के बारे में बहुत कुछ बदलते हैं - 2 के बजाय 1 या 3 मान जोड़ें, या अपने आंतरिक लूप की पुनरावृत्तियों की संख्या 1 से कम करें, तो यह उम्मीद के मुताबिक अपवाद फेंक देगा।

(यह निर्दिष्ट व्यवहार के बजाय एक कार्यान्वयन विवरण है - और यह एक कार्यान्वयन विवरण है जिसे एक बहुत ही दुर्लभ मामले में बग के रूप में देखा जा सकता है। यह वास्तविक कार्यक्रम में समस्या का कारण बनता है, यह देखना बहुत ही असामान्य होगा।)


5
बस संदर्भ के लिए: प्रासंगिक स्रोत कोड , ध्यान दें कि _versionफ़ील्ड एक है int
लुकास ट्रेजेनिव्स्की

1
हाँ, यह ठीक से स्थापित किया गया है ताकि लूप के खत्म होने के बाद, _version का मान -2 हो .... फिर 6 और 7 को जोड़कर इसे 0 कर दिया जाता है, जिससे सूची ऐसी दिखती है जैसे यह असम्बद्ध है।
काज

4
मुझे यकीन नहीं है कि इसे "कार्यान्वयन विवरण" कहा जाना चाहिए, क्योंकि उस कार्यान्वयन निर्णय का एक पक्ष प्रभाव है, भले ही ऐसा होने की संभावना न हो, वास्तविक है। युक्ति (या कम से कम डॉक्टर) का कहना है कि यह एक फेंकना चाहिए InvalidOperationException, जो वास्तव में हमेशा सच नहीं होता है। बेशक यह "कार्यान्वयन विस्तार" की परिभाषा पर निर्भर करता है।
ken2k

3
जॉन स्कीट, क्या आप भाषा डिजाइनर की प्रोग्रामिंग कर रहे हैं? (Google पर कुछ भी संबंधित नहीं पाया गया) थोड़ा उत्सुक था कि आपको यह ज्ञान क्यों है। स्टैक ओवरफ्लो की "पावर" को देखने के लिए यह सवाल थोड़ा चिढ़ाने वाला था।
लीनिंगऑनस्काइ

6
@LyingOnTheSky: नहींं, हालाँकि मुझे C # भाषा का अनुसरण करने और आलोचना करने के मामले में एक भाषा डिजाइनर होने के नाते खेलना पसंद है। मैं CMA 5 के मानकीकरण के लिए ECMA-334 तकनीकी समूह पर भी हूं ... इसलिए मुझे छेद चुनने के लिए मिलता है, लेकिन वास्तविक भाषा डिजाइन का काम नहीं करता हूं :)
जॉन स्कीट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.