เหตุใดรหัสนี้จึงแสดงข้อความว่า 'Collection was modified' แต่เมื่อฉันทำซ้ำบางสิ่งก่อนหน้านั้นมันไม่?


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วงในมันจะพ่นออกมาเห็นได้ชัดว่าเป็นเพราะเราเปลี่ยนแปลงคอลเล็กชัน

ตอนนี้ถ้าคุณไม่ใส่ความคิดเห็นทำไมลูปนี้ถึงอนุญาตให้เราเพิ่มสองรายการนั้น? มันใช้เวลาสักครู่ในการรันเหมือนครึ่งนาที (บน Pentium CPU) แต่มันไม่โยนและสิ่งที่ตลกคือมันส่งออก:

ภาพ

มันค่อนข้างคาดหวัง แต่มันบ่งบอกว่าเราเปลี่ยนได้และมันเปลี่ยนคอลเลกชั่นได้จริง มีความคิดว่าเหตุใดจึงเกิดพฤติกรรมเช่นนี้


2
นั่นดูน่าสนใจ. ฉันสามารถสร้างพฤติกรรมซ้ำได้ แต่ไม่ใช่ถ้าฉันเปลี่ยนลูปภายในจาก Int.MaxValue เป็นค่าเช่น 100
Steve

คุณรอนานแค่ไหน? ใช้เวลาพอสมควรในการint.MaxValueทำซ้ำให้เสร็จ...
Jon Skeet

1
ฉันเชื่อว่า foreach จะตรวจสอบเพื่อดูว่ามีการแก้ไขคอลเลกชันที่จุดเริ่มต้นของแต่ละลูปหรือไม่ ... ดังนั้นการเพิ่มแล้วลบรายการภายในแต่ละลูปจึงไม่ทำให้เกิดข้อผิดพลาดใด ๆ
Kaz

6
คุณอาจตอบคำถามนี้ได้ด้วยตนเองโดยดูที่แหล่งอ้างอิงและดูว่าการตรวจจับการเปลี่ยนแปลงทำงานอย่างไร ไม่ใช่ทุกคนที่รู้ว่าแหล่งอ้างอิงมีอยู่เพียงแค่กระจายคำ :)
Christopher Currens

2
เพียงแค่อยากรู้: คุณมีปัญหานี้ในชิ้นส่วนของรหัสจริงหรือไม่?
ken2k

คำตอบ:


119

ปัญหาคือวิธีที่List<T>ตรวจพบการปรับเปลี่ยนคือการเก็บฟิลด์เวอร์ชันประเภทintเพิ่มขึ้นในการแก้ไขแต่ละครั้ง ดังนั้นถ้าคุณได้ทำตรงหลาย 2 บางส่วน32การปรับเปลี่ยนรายการระหว่างซ้ำก็จะทำให้การปรับเปลี่ยนผู้ที่มองไม่เห็นเท่าที่ตรวจสอบการเป็นห่วง (มันจะล้นจากint.MaxValueถึงint.MinValueและกลับไปสู่ค่าเริ่มต้นในที่สุด)

หากคุณเปลี่ยนแปลงอะไรเกี่ยวกับโค้ดของคุณให้เพิ่ม 1 หรือ 3 ค่าแทนที่จะเป็น 2 หรือลดจำนวนการวนซ้ำของวงในของคุณลง 1 มันจะทำให้เกิดข้อยกเว้นตามที่คาดไว้

(นี่เป็นรายละเอียดการนำไปใช้งานแทนที่จะเป็นลักษณะการทำงานที่ระบุ - และเป็นรายละเอียดการใช้งานซึ่งสามารถสังเกตได้ว่าเป็นจุดบกพร่องในกรณีที่หายากมากอย่างไรก็ตามมันจะผิดปกติมากที่จะเห็นว่ามันทำให้เกิดปัญหาในโปรแกรมจริง)


5
สำหรับการอ้างอิง: ซอร์สโค้ดที่เกี่ยวข้องโปรดทราบว่า_versionฟิลด์นี้เป็นไฟล์intไฟล์.
Lucas Trzesniewski

1
ใช่มันถูกตั้งค่าอย่างถูกต้องดังนั้นหลังจากที่ for loop เสร็จสิ้น _version จะมีค่า -2 .... จากนั้นการเพิ่ม 6 และ 7 จะทำให้เป็น 0 ทำให้รายการดูเหมือนว่าไม่มีการแก้ไข
Kaz

4
ฉันไม่แน่ใจว่าสิ่งนี้ควรเรียกว่า "รายละเอียดการนำไปใช้งาน" เนื่องจากมีผลข้างเคียงจากการตัดสินใจติดตั้งซึ่งแม้ว่าจะไม่น่าเกิดขึ้น แต่ก็เป็นเรื่องจริง ข้อมูลจำเพาะ (หรืออย่างน้อยที่สุดก็คือเอกสาร) บอกว่าควรโยนทิ้งInvalidOperationExceptionซึ่งจริงๆแล้วไม่จริงเสมอไป แน่นอนว่าสิ่งนี้ขึ้นอยู่กับคำจำกัดความของ "รายละเอียดการนำไปใช้งาน"
ken2k

3
Jon Skeet คุณเป็นนักออกแบบภาษาโปรแกรมหรือไม่? (ไม่พบสิ่งที่เกี่ยวข้องใน Google) อยากรู้นิดหน่อยว่าทำไมคุณถึงมีความรู้นี้ด้วย คำถามนี้เป็นการหยอกล้อเพื่อดู "พลัง" ของ Stack Overflow
LyingOnTheSky

6
@LyingOnTheSky: ไม่แม้ว่าฉันจะชอบเล่นเป็นนักออกแบบภาษาในแง่ของการติดตามและวิจารณ์ภาษา C # ฉันอยู่ในกลุ่มเทคนิค ECMA-334 สำหรับการสร้างมาตรฐาน C # 5 ... ดังนั้นฉันจึงเลือกรู แต่ไม่ได้ทำงานออกแบบภาษาจริง :)
Jon Skeet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.