Bu kod neden 'Koleksiyon değiştirildi' mesajı veriyor, ancak ondan önce bir şeyi yinelediğimde değil mi?


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;
}

İç fordöngüyü yorumlarsanız, fırlatır, bunun sebebi koleksiyonda değişiklikler yapmış olmamızdır.

Şimdi eğer açıklamayı kaldırırsanız, bu döngü neden bu iki öğeyi eklememize izin veriyor? Yarım dakika gibi çalıştırmak biraz zaman alıyor (Pentium CPU'da), ancak fırlatmıyor ve komik olan şu çıktı:

Resim

Biraz beklenen bir şeydi, ama değiştirebileceğimizi gösteriyor ve aslında koleksiyonu değiştiriyor. Bu davranışın neden meydana geldiğine dair bir fikriniz var mı?


2
İlginç. Davranışı yeniden oluşturabilirim, ancak dahili döngüyü Int.MaxValue'den 100 gibi bir değere değiştirirsem
Steve

Ne kadar bekledin int.MaxValueYinelemeleri bitirmek epey zaman alıyor ...
Jon Skeet

1
Foreach'in koleksiyonun her döngünün başında değiştirilip değiştirilmediğini kontrol ettiğine inanıyorum .... bu nedenle her döngüde öğeyi eklemek ve sonra kaldırmak herhangi bir hata oluşturmaz.
Kaz

6
Referans kaynağına bakarak ve değişiklik tespitinin nasıl çalıştığını görerek bu soruyu kendiniz cevaplayabilirsiniz . Referans kaynağın var olduğunu bile herkes bilmiyor, sadece kelimeyi
yayıyorum

2
Merak ediyorum: Bu sorunu gerçek dünyadaki bir kod parçasında mı yaşadınız?
ken2k

Yanıtlar:


119

Sorun, List<T>değişiklikleri tespit etmenin yolunun int, her değişiklikten sonra onu artırarak , türden bir sürüm alanını tutmasıdır. Yaptığınız nedenle, tam olarak 2 bazılarının birden 32 tekrarlamalar arasındaki listeye değişiklikler, bu kadarıyla algılama söz konusu olduğunda görünmez bu modifikasyonları hale getirecek. (Bu gelen taşar int.MaxValueiçin int.MinValueve sonunda başlangıç değerine geri almak.)

Kodunuzla ilgili hemen hemen her şeyi değiştirirseniz - 2 yerine 1 veya 3 değer eklerseniz veya iç döngünüzün yineleme sayısını 1 düşürürseniz, beklendiği gibi bir istisna atar.

(Bu, belirli bir davranıştan ziyade bir uygulama ayrıntısıdır - ve çok nadir durumlarda bir hata olarak görülebilen bir uygulama ayrıntısıdır. Bununla birlikte, gerçek bir programda bir soruna neden olduğunu görmek çok alışılmadık bir durumdur.)


5
Sadece referans için: ilgili kaynak kodu , _versionalanın bir int.
Lucas Trzesniewski

1
Evet, tam olarak ayarlandı, böylece for döngüsü bittikten sonra, _version -2 .... değerine sahip olacak ve sonra 6 ve 7 ekleyerek 0'a koyacak ve listeyi değiştirilmemiş gibi gösterecek.
Kaz

4
Bunun bir "uygulama ayrıntısı" olarak adlandırılması gerektiğinden emin değilim, çünkü bu uygulama kararının bir yan etkisi var, olması muhtemel olmasa bile gerçek. Spesifikasyon (veya en azından belge) InvalidOperationException, aslında her zaman doğru olmayan bir atması gerektiğini söylüyor . Elbette bu, "uygulama ayrıntısı" tanımına bağlıdır.
ken2k

3
Jon Skeet, programlama dili tasarımcısı mısınız? (Google'da ilgili bir şey bulamadım) Bu bilgiye neden sahip olduğunuzu biraz merak ediyorum. Bu soru, Stack Overflow'un "gücünü" görmek için biraz alay konusu oldu.
LyingOnTheSky

6
@LyingOnTheSky: Hayır, ancak C # dilini takip etme ve eleştirme açısından bir dil tasarımcısı olmayı sevmeme rağmen. Ayrıca C # 5'i standartlaştırmak için ECMA-334 teknik grubundayım ... bu yüzden delikleri seçiyorum ama gerçek dil tasarım işini yapmıyorum :)
Jon Skeet
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.