2012-07-15 15 views
12

Non capisco perché il metodo di estensione List<T>.ForEach() implementa un ciclo for sotto il cofano. Ciò apre la possibilità che la collezione venga modificata. Un normale foreach genererà un'eccezione in questo caso, quindi sicuramente ForEach() dovrebbe reagire nello stesso modo?Perché l'Elenco <T>. PerEgni() implementa un ciclo for?

Se si DEVE mutare una raccolta per qualsiasi motivo, quindi sicuramente si dovrebbe iterare manualmente attraverso la raccolta in un ciclo for?

Qui sembra esserci un po 'di contraddizione semantica tra foreach e List<T>.ForEach().

Mi manca qualcosa?

+5

"Questo apre la possibilità che la collezione venga modificata ". [Esattamente il motivo per cui ora è passato da .NET per le app in stile Metro.] (Http://stackoverflow.com/questions/10299458/is-the-listt-foreach-extension-method-gone/10299492#10299492) Ho hum . – BoltClock

+3

I commenti (di disapprovazione) di Eric Lippert [commenti su '.ForEach'] (http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx) valgono sempre la pena in questo contesto. –

+0

a proposito: http://stackoverflow.com/questions/10299458/is-the-listt-foreach-extension-method-gone – user287107

risposta

4

Solo un membro del team BCL può dircelo con certezza, ma probabilmente era solo una svista che è possibile modificare l'elenco da .

In primo luogo, la risposta di David B non ha senso per me. È List<T>, non C#, che controlla se si modifica l'elenco all'interno di un ciclo foreach e si genera un InvalidOperationException se lo si fa. Non ha niente a che fare con la lingua che stai usando.

In secondo luogo, c'è questo avvertimento nel documentation:

Modifica della collezione sottostante nel corpo dell'azione <T> delegato non è supportata e causa un comportamento indefinito.

Trovo improbabile che il team BCL abbia voluto un metodo così semplice come ForEach per avere un comportamento non definito.

In terzo luogo, come di .NET 4.5, sarà gettare un InvalidOperationException se il delegato modifica l'elenco. Se un programma dipende dal vecchio comportamento, it will stop working quando viene ricompilato su .NET 4.5 di destinazione. Il fatto che Microsoft sia disposta ad accettare questo cambiamento radicale suggerisce fortemente che il comportamento originale non era intenzionale e non dovrebbe essere invocato.

Per riferimento, ecco come implementata in .NET 4.0, direttamente dalla fonte di riferimento:

public void ForEach(Action<T> action) { 
    if(action == null) { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    Contract.EndContractBlock(); 

    for(int i = 0 ; i < _size; i++) { 
     action(_items[i]); 
    } 
} 

Ed ecco come è stato cambiato in .NET 4.5:

public void ForEach(Action<T> action) { 
    if(action == null) { 
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); 
    } 
    Contract.EndContractBlock(); 

    int version = _version; 

    for(int i = 0 ; i < _size; i++) { 
     if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) { 
      break; 
     } 
     action(_items[i]); 
    } 

    if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) 
     ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); 
} 
+0

E forse anche motivi di prestazioni? – nawfal

+0

@nawfal: Probabilmente no. Il costo del controllo di 'if (version! = _version)' è improbabile che sia misurabile. –

5

Poiché List.ForEach seguito della definizione da MSDN:

esegue l'azione specificata su ogni elemento dell'elenco.

Ciò significa che Action eseguita sull'elemento, può potenzialmente cambiare elemento, o raccolta stessa. In questo caso, non c'è altro modo (se non si crea una costosa raccolta di cloni, se è possibile), quindi utilizzare un semplice for.

Se si modifica la raccolta durante l'iterazione in foreach, si solleva, naturalmente, un'eccezione.

+1

Ah, per definizione è corretto allora. Sento ancora che è leggermente fuorviante. Immagino che questo sia il motivo di tutte le polemiche (e del motivo per cui lo stanno lasciando dalla metropolitana di .NET, come ha sottolineato BoltClock)? – davenewza

+2

@davenewza No, la documentazione collegata dice _Modificare la raccolta sottostante nel corpo di 'Azione ' il delegato non è supportato e non [?] Causa un comportamento indefinito._ Oppure, se uno preferisce l'inglese per qualche motivo: _Modifica della raccolta sottostante nel corpo dell'azione '' il delegato non è supportato e causa un comportamento indefinito. Quindi dicono che non dovresti modificare 'Lista <>' nel delegato 'action'. –

5

foreach è un elemento di linguaggio C#. Gioca secondo le regole di C#.

è un metodo .NET Framework. Funziona secondo le regole di .NET, dove foreach non esiste.

Questo è un esempio di confusione "linguaggio vs quadro". I metodi quadro devono funzionare in molte lingue e le lingue (di solito) hanno una semantica contraddittoria.

Un altro esempio di questa confusione "linguaggio vs quadro" è il cambio di rottura a Enumerable.Cast tra .net 3 e .NET 3.5. In .NET 3, Cast usato semantica C#. In .net 3.5, è stato modificato per utilizzare la semantica di .net.