Perché questo codice genera "Collection was modified", ma quando itero qualcosa prima di esso, non lo fa?


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

Se commentate il forciclo interno , viene generato, è ovviamente perché abbiamo apportato modifiche alla raccolta.

Ora, se lo rimuovi dal commento, perché questo ciclo ci consente di aggiungere questi due elementi? Ci vuole un po 'per eseguirlo come mezzo minuto (su CPU Pentium), ma non lancia, e la cosa divertente è che emette:

Immagine

Era un po 'previsto, ma indica che possiamo cambiare e in realtà cambia la collezione. Qualche idea sul perché si verifica questo comportamento?


2
Interessante. Potrei riprodurre il comportamento, ma non se modifico il ciclo interno da Int.MaxValue a un valore come 100
Steve

Quanto tempo hai aspettato? Ci vuole un po 'per finire le int.MaxValueiterazioni ...
Jon Skeet

1
Credo che il foreach controlli per vedere se la raccolta è stata modificata all'inizio di ogni ciclo ... quindi l'aggiunta e la rimozione dell'elemento all'interno di ogni ciclo non genera errori.
Kaz

6
Potresti essere stato in grado di rispondere tu stesso a questa domanda guardando la fonte di riferimento e vedendo come funzionava il rilevamento delle modifiche. Non tutti sanno che la fonte di riferimento esiste anche, solo spargendo la voce :)
Christopher Currens

2
Solo per curiosità: hai riscontrato questo problema in un pezzo di codice del mondo reale?
ken2k

Risposte:


119

Il problema è che il modo in cui List<T>rileva le modifiche è mantenere un campo di versione, di tipo int, incrementandolo ad ogni modifica. Pertanto, se hai apportato esattamente un multiplo di 2 32 modifiche all'elenco tra le iterazioni, renderà quelle modifiche invisibili per quanto riguarda il rilevamento. (Andrà in overflow da int.MaxValuea int.MinValuee alla fine tornerà al suo valore iniziale.)

Se modifichi praticamente qualcosa del tuo codice: aggiungi 1 o 3 valori invece di 2 o abbassa il numero di iterazioni del tuo ciclo interno di 1, allora verrà generata un'eccezione come previsto.

(Questo è un dettaglio di implementazione piuttosto che un comportamento specificato - ed è un dettaglio di implementazione che può essere osservato come un bug in casi molto rari. Tuttavia, sarebbe molto insolito vederlo causare un problema in un programma reale.)


5
Solo per riferimento: codice sorgente pertinente , nota che il _versioncampo è un file int.
Lucas Trzesniewski

1
Sì, è impostato correttamente in modo che al termine del ciclo for, _version ha un valore di -2 .... quindi aggiungendo 6 e 7 lo si mette a 0, facendo sembrare l'elenco non modificato.
Kaz

4
Non sono sicuro che questo dovrebbe essere chiamato un "dettaglio di implementazione", perché c'è un effetto collaterale di quella decisione di implementazione, che anche se è improbabile che accada, è reale. La specifica (o almeno il documento) dice che dovrebbe lanciare un InvalidOperationException, che in realtà non è sempre vero. Ovviamente questo dipende dalla definizione di "dettaglio implementativo".
ken2k

3
Jon Skeet, sei un progettista di linguaggi di programmazione? (Non ho trovato nulla di correlato su Google) Un po 'curioso di sapere perché anche tu hai questa conoscenza. Questa domanda è stata un po 'una presa in giro per vedere il "potere" di Stack Overflow.
LyingOnTheSky

6
@LyingOnTheSky: No, anche se mi piace giocare a fare il progettista di linguaggi in termini di seguire e criticare il linguaggio C #. Sono anche nel gruppo tecnico ECMA-334 per la standardizzazione di C # 5 ... quindi riesco a scegliere i buchi ma non faccio il vero lavoro di progettazione del linguaggio :)
Jon Skeet
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.