2012-07-19 21 views
7

Tengo una consulta linq, cuyos resultados repito en un ciclo foreach.consulta foreach y linq - necesito ayuda para tratar de entender, por favor

La primera es una pregunta que agarra una colección de controles de un panel de diseño mesa, a continuación, iterar sobre la colección y quitar los controles de la TableLayoutPanel así:

var AllItems = (from Item in this.tableLayoutPanel1.Controls.OfType<ItemControl>() 
       select Item); 

foreach (ItemControl item in AllItems) 
{ 
    Trace.WriteLine("Removing " + item.ToString()); 
    this.tableLayoutPanel1.Controls.Remove(item); 
    item.Dispose(); 
} 

Lo anterior no hacer lo que yo esperado (es decir, lanzar un error), elimina solo la mitad de los controles (los ODD numerados) parece que en cada iteración AllItems reduce a sí mismo, y aunque la colección subyacente se está modificando no se produce ningún error.

Si yo mismo tipo con una matriz de cadenas:

 string[] strs = { "d", "c", "A", "b" }; 
     List<string> stringList = strs.ToList(); 

     var allitems = from letter in stringList 
         select letter; 

     foreach (string let in allitems) 
     { 
      stringList.Remove(let); 

     } 

Esta vez Visual Studio genera un error (como se esperaba) quejándose de que la colección subyacente ha cambiado.

¿Por qué el primer ejemplo tampoco explota?

Hay algo sobre Iterators/IEnumerable que no estoy entendiendo aquí y me pregunto si alguien podría ayudarme a entender lo que está pasando bajo el capó con linq y foreach.

(Soy consciente de que puedo curar estas dos cuestiones para AllItems.ToList(); antes de iterar, pero me gustaría entender por qué el segundo ejemplo se emite un error y el primero no lo hace)

Respuesta

10

Por qué ¿El primer ejemplo no explota también?

La aplicación IEnumerator dentro tableLayoutPanel1.Controls no está realizando las comprobaciones adecuadas para ver si la colección se ha modificado. Esto no significa que se trata de una operación segura (ya que los resultados no son apropiados), sino más bien que la clase no se implementó de una manera que genera una excepción (como probablemente debería).

La excepción que se plantea al enumerar a través de List<T> y eliminar un elemento se genera realmente mediante el tipo List<T>.Enumerator, y no es una característica de lenguaje de "propósito general".

Tenga en cuenta que esta es una buena característica del tipo List<T>.Enumerator, como la documentación IEnumerator<T> afirma explícitamente:

Un enumerador sigue siendo válido, siempre y cuando la colección se mantiene sin cambios. Si se realizan cambios en la colección, como agregar, modificar o eliminar elementos, el enumerador queda irrevocablemente invalidado y su comportamiento no está definido.

Si bien no existe un contrato que requiera una excepción, es beneficioso para las implementaciones hacerlo cuando ingresa en el reino del "comportamiento indefinido".

En este caso, la implementación del enumerador de clase TableLayoutControlCollection no está realizando comprobaciones adicionales, por lo que está viendo lo que sucede cuando utiliza una característica donde el "comportamiento no está definido". (En este caso, quita cualquier otro control.)

+0

es la razón para este problema no está relacionado con el hecho de que 'Enumerable.OfType' se implementa mediante el uso de ejecución diferida, por lo tanto, la secuencia que siempre cambian en cada enumeración? –

+2

@TimSchmelter Bueno, 'OfType ' simplemente se enumera mediante el uso del enumerador de origen directamente - mientras no se está haciendo la comprobación, lo mismo sucedería si se enumeran los objetos, y poner la verificación dentro del bucle. El enumerador subyacente no está realizando una comprobación de si la colección de control se ha modificado. –

+0

@TimSchmelter: sospecho que todas las operaciones LINQ podrían eliminarse del código postal original, y se comportaría exactamente de la misma manera. Como dice Reed, este es el resultado de la implementación del enumerador. OfType no tiene nada que ver con eso. – StriplingWarrior

2

así que en mi primer caso, la expresión linq se vuelve a evaluar en cada iteración del bucle foreach? o está linq obteniendo el IEnumerator de la colección tablelayoutpanel.controls?

pensar en ella como si estuviera sucediendo como éste, que es lo que está sucediendo en ambos sus casos:

foreach (string l in (from letter in stringList select letter)) 
    { 
     stringList.Remove(l); 
    } 

El pseudo código anterior muestra que a medida que tratan de eliminar un elemento que está esencialmente en el medio de una enumeración de la colección stringList. Lo que Reed está diciendo es que en el segundo caso el enumerador del stringList se está asegurando de que nadie esté jugando con él mientras se encuentra en el medio de la enumeración.

Está diciendo que en el caso de los controles, el enumerador de controles está felizmente de acuerdo y no está comprobando si alguien está jugando con sus datos.

Para completar, comparar estos dos ejemplos:

Esta consulta para letters será reevaluado cada vez que se toca letters que incluye el tiempo que estamos en el bucle foreach:

IEnumerable<string> letters = from letter in stringList select letter); 

    // everytime we hit this we're going to hit the stringList collection 
    foreach (string l in letters) 
    { 
     stringList.Remove(l); 
    } 

Esta consulta utiliza la ToList() que completa de inmediato el letters y ya no tocaremos la colección stringList en el bucle foreach.

List<string> letters = (from letter in stringList select letter).ToList() 

    // because we ran ToList(), we will no longer enumerate stringList 
    foreach (string l in letters) 
    { 
     stringList.Remove(l); 
    } 
+0

Gracias Brad. Lo entiendo ahora. @todas las personas que respondieron: Gracias, esto realmente ha aclarado este tema en mi mente. ¡Ustedes molan! – John

0

Prueba esto:

var AllItems = (from Item in this.tableLayoutPanel1.Controls.OfType<ItemControl>() 
       select Item); 


    int totalCount = AllItems .Count; 
    for (int i = 0; i < totalCount; i++) 
     { 
      AllItems .RemoveAt(0); 
     } 
Cuestiones relacionadas