2012-07-15 7 views
12

No entiendo por qué el método de extensión List<T>.ForEach() implementa un lazo for debajo del capó. Esto abre la posibilidad de que la colección se modifique. Un foreach normal emitirá una excepción en este caso, así que seguramente ForEach() debería reaccionar de la misma manera?¿Por qué la lista <T> .ForEach() implementa un bucle for?

Si DEBE mutar una colección por cualquier razón, entonces seguramente debería iterar manualmente a través de la colección en un bucle for?

Parece que hay una pequeña contradicción semántica entre foreach y List<T>.ForEach().

¿Echo de menos algo?

+5

"Esto abre la posibilidad de que la colección se modifique ". [Exactamente por qué ahora se ha ido de .NET para aplicaciones de estilo Metro.] (Http://stackoverflow.com/questions/10299458/is-the-listt-foreach-extension-method-gone/10299492#10299492) Ho hum . – BoltClock

+3

Eric Lippert (desaprobación) [los comentarios sobre '.ForEach'] (http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx) siempre merecen una lectura en este contexto. –

+0

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

Respuesta

4

Solo un miembro del equipo de BCL puede decirnos con seguridad, pero probablemente fue solo un descuido que le permite modificar la lista.

En primer lugar, la respuesta de David B no tiene sentido para mí. Es List<T>, no C#, que comprueba si modifica la lista dentro de un bucle foreach y arroja un InvalidOperationException si lo hace. No tiene nada que ver con el idioma que estás usando.

En segundo lugar, hay una advertencia en el documentation:

Modificación de la colección subyacente en el cuerpo de la acción no es compatible <T> delegado y causa un comportamiento indefinido.

No me parece probable que el equipo de BCL haya querido que un método tan simple como ForEach tenga un comportamiento indefinido.

En tercer lugar, a partir de .NET 4.5, se producirá una InvalidOperationException si el delegado modifica la lista. Si un programa depende del comportamiento anterior, it will stop working cuando se vuelve a compilar para .NET 4.5. El hecho de que Microsoft esté dispuesto a aceptar este cambio radical sugiere que el comportamiento original no fue intencionado y no se debe confiar en él.

Para referencia, aquí es como se implementa en .NET 4.0, directamente desde la fuente de referencia:

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

Y así es como se ha cambiado en .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

¿Y posiblemente también por razones de rendimiento? – nawfal

+0

@nawfal: Probablemente no. El costo de verificar 'if (version! = _version)' es poco probable que se pueda medir. –

5

Debido List.ForEach siguiendo la definición de MSDN:

realiza la acción especificada en cada elemento de la lista.

Eso significa que Action ejecutada sobre el elemento, puede elemento, o colección en sí potencialmente cambiar. En este caso, no hay otra manera (si no se está creando la colección de clones de costy, if es posible) de permitirse esto, luego usando un simple for.

Si cambia la colección durante la iteración en foreach, naturalmente, se genera una excepción.

+1

Ah, por definición, es correcto entonces. Todavía siento que es un poco engañoso. Supongo que esa es la razón de toda la controversia (y la razón por la cual la están eliminando del .NET metro, como BoltClock señaló). – davenewza

+2

@davenewza No, la documentación vinculada dice _Modificare la raccolta sottostante nel corpo di 'Acción ' il delegato non è supportato e non [?] Causa un comportamiento indefinito._ O, si prefiere Inglés por alguna razón: _Modificando la colección subyacente en el cuerpo del delegado 'Acción ' no es compatible y causa un comportamiento indefinido. Por lo tanto, dicen que no debe modificar 'List <>' en el delegado 'action'. –

5

foreach es un elemento de lenguaje C#. Juega según las reglas de C#.

es un método de .NET Framework. Juega según las reglas de .NET, donde foreach no existe.

Este es un ejemplo de confusión entre "lenguaje frente a marco". Los métodos de Framework deben funcionar en muchos idiomas, y los lenguajes (generalmente) tienen una semántica contradictoria.

Otro ejemplo de esta confusión de "lenguaje frente a marco" es el cambio de rotura a Enumerable.Cast entre .net 3 y .NET 3.5. En .NET 3, Cast utilizó la semántica de C#. En .net 3.5, se cambió para usar la semántica de .net.

Cuestiones relacionadas