2009-10-21 11 views
5

Supongamos el siguiente código:Pregunta sobre foreach y los delegados

foreach(Item i on ItemCollection) 
{ 
    Something s = new Something(); 
    s.EventX += delegate { ProcessItem(i); }; 
    SomethingCollection.Add(s); 
} 

Por supuesto, esto es un error ya que todos los puntos de delegados al mismo artículo. La alternativa es:

foreach(Item i on ItemCollection) 
{ 
    Item tmpItem = i; 
    Something s = new Something(); 
    s.EventX += delegate { ProcessItem(tmpItem); }; 
    SomethingCollection.Add(s); 
} 

En este caso, todos los delegados señalan su propio elemento.

¿Qué pasa con este enfoque? Hay alguna otra mejor solución?

+0

¿Podría publicar el código completo que compila y muestra la diferencia? – empi

+0

Puede descompilar primera pieza de código en C# 1.0 y verá cuál es la diferencia –

Respuesta

11

ACTUALIZACIÓN: Existe una amplia análisis y comentarios sobre este tema aquí :

http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/


Este es un problema reportado con mucha frecuencia; generalmente se informa como un error del compilador, pero de hecho el compilador está haciendo lo correcto de acuerdo con la especificación. Las funciones anónimas cercanas sobre las variables , no valores, y sólo hay una única variable de bucle foreach. Por lo tanto, cada lambda se cierra sobre la misma variable y, por lo tanto, obtiene el valor actual de esa variable.

Esto es sorprendente que casi todo el mundo, y da lugar a mucha confusión y muchos informes de errores. Estamos considerando cambiar la especificación e implementación de una versión futura hipotética de C# para que la variable de bucle se declara lógicamente dentro de la estructura iterativa, dando un "fresco" variable de cada iteración del bucle.

Esto sería un cambio de rotura, pero sospecho que el número de personas que dependen de este comportamiento extraño es bastante bajo. Si tiene opiniones sobre este tema, siéntase libre de agregar comentarios a las publicaciones del blog mencionadas anteriormente en la actualización. ¡Gracias!

+1

y no es mejor mostrar una advertencia de compilador en lugar de cambiar el comportamiento? – FerranB

+1

De hecho, una advertencia del compilador * podría * estar garantizada en esta situación. No está claro cuál es * mejor *. Consideraremos ambos. –

0

El problema que se enfrentan aquí está relacionada con la construcción del lenguaje, tales como el cierre . El segundo fragmento de código soluciona el problema.

5

El segundo trozo de código es casi el mejor enfoque que puede conseguir todo lo demás se mantiene igual. No es posible crear una propiedad en Something que toma Item. A su vez, el código del evento podría acceder al Item del remitente del evento o podría incluirse en el evento para el evento. De ahí la eliminación de la necesidad del cierre.

Personalmente He añadido "Eliminación de Cierre innecesario" como refactorización que vale la pena ya que puede ser difícil razonar sobre ellos.

+0

sí lo que estaba pensando, pero mucho más elocuente :) – Hath

0
foreach(Item i on ItemCollection) 
{ 
    Something s = new Something(i); 
    s.EventX += (sender, eventArgs) => { ProcessItem(eventArgs.Item);}; 
    SomethingCollection.Add(s); 
} 

sería no sólo tiene que pasar 'i' en en su Clase 'algo' y lo uso en evento args de EventX

1

Si ItemCollection es un (generic) List puede utilizar su ForEach -method. Se le dará un alcance más fresco por i:

ItemCollection.ForEach(
    i => 
    { 
     Something s = new Something(); 
     s.EventX += delegate { ProcessItem(i); }; 
     SomethingCollection.Add(s); 
    }); 

o puede utilizar cualquier otra adecuada Linq -method - como Select:

var somethings = ItemCollection.Select(
     i => 
     { 
      Something s = new Something(); 
      s.EventX += delegate { ProcessItem(i); }; 
      return s; 
     }); 
foreach(Something s in somethings) 
    SomethingCollection.Add(s); 
+0

no recomendaría estos como soluciones, aunque ... –