2011-06-17 18 views
10

Intento evolucionar mi comprensión de los efectos secundarios y cómo deben controlarse y aplicarse.Paradigmas de C#: Efectos secundarios en las listas

En la siguiente lista de vuelos, quiero establecer una propiedad de cada vuelo satisfacer una condiciones:

IEnumerable<FlightResults> fResults = getResultsFromProvider(); 

//Set all non-stop flights description 
fResults.Where(flight => flight.NonStop) 
     .Select(flight => flight.Description = "Fly Direct!"); 

En esta expresión, que tiene un efecto secundario en mi lista. De mi conocimiento limitado lo sé por ej. "LINQ solo se utiliza para consultas " y "Hay solo unas pocas operaciones en las listas y asignando o establecer valores no es uno de ellos" y "las listas deben ser inmutables".

  • ¿Qué problema hay en mi declaración de LINQ anterior y cómo se debe cambiar?
  • ¿Dónde puedo obtener más información sobre paradigmas fundamentales en el escenario que he descrito anteriormente?
+12

Utilice un bucle "foreach" para inducir un efecto secundario.La selección es para proyección, no para actualización. Es por eso que se llama "Seleccionar" y no "Actualizar". –

+1

pregunta relacionada aquí http://stackoverflow.com/questions/5632222/linq-side-effects – nawfal

Respuesta

5

Su código LINQ no "directamente" viola las directrices que mencionas, porque no se está modificando la lista en sí; solo está modificando alguna propiedad en el contenido de la lista.

Sin embargo, la objeción principal que impulsa estas pautas sigue siendo: no debe modificar los datos con LINQ (también está abusando de Select para realizar sus efectos secundarios).

No modificando cualquier información se puede justificar con bastante facilidad. Considere este fragmento:

fResults.Where(flight => flight.NonStop) 

¿Ve dónde está esto modificando las propiedades de vuelo? Tampoco lo harán muchos programadores de mantenimiento, ya que dejarán de leer después del Where - el código que sigue es obviamente libre de efectos secundarios ya que esta es una consulta, ¿no?

[Nitpick: Ciertamente, ver una consulta cuyo valor de retorno no se conserve es un claro indicio de que la consulta tiene efectos secundarios o que el código debería haberse eliminado; en cualquier caso, ese "algo está mal". . Pero es mucho más fácil de decir que cuando hay sólo 2 líneas de código para mirar en lugar de páginas y páginas]

Como solución correcta, yo recomendaría esto:

foreach (var x in fResults.Where(flight => flight.NonStop)) 
{ 
    x.Description = "Fly Direct!"; 
} 

bastante fácil ambos escriben y leen.

2

Me gusta usar foreach cuando estoy realmente cambiando algo. Algo así como

foreach (var flight in fResults.Where(f => f.NonStop)) 
{ 
    flight.Description = "Fly Direct!"; 
} 

y lo mismo ocurre con Eric Lippert en su article acerca de por qué LINQ no tiene un método ParaCada ayudante.

Pero podemos profundizar un poco más aquí. Me opongo filosóficamente a proporcionar ese método, por dos razones.

La primera razón es que hacerlo viola los principios de programación funcional en los que se basan todos los demás operadores de secuencia. Claramente, el único propósito de una llamada a este método es causar efectos secundarios.

+0

Buen artículo que explica algunas ideas centrales con respecto a mi pregunta gracias. – Pierre

8

tienes dos maneras de lograr que la forma de LINQ:

  1. explícita foreach bucle

    foreach(Flight f in fResults.Where(flight => flight.NonStop)) 
        f.Description = "Fly Direct!"; 
    
  2. con un operador de ForEach, hechos por los efectos secundarios:

    fResults.Where(flight => flight.NonStop) 
         .ForEach(flight => flight.Description = "Fly Direct!"); 
    

La primera manera es bastante pesada para una tarea tan simple, la segunda manera solo debe usarse con cuerpos muy cortos.

Ahora, podría preguntarse por qué no hay un operador ForEach en la pila LINQ. Es bastante simple: se supone que LINQ es una forma funcional de expresar operaciones de consulta, lo que significa especialmente que no se supone que ninguno de los operadores tenga efectos secundarios. El equipo de diseño decidió no agregar un operador ForEach a la pila porque el único uso es su efecto secundario.

Una aplicación habitual del operador ForEach sería así:

public static class EnumerableExtension 
{ 
    public static void ForEach<T> (this IEnumerable<T> source, Action<T> action) 
    { 
    if(source == null) 
     throw new ArgumentNullException("source"); 

    foreach(T obj in source) 
     action(obj); 

    } 
} 
+0

+1: Pero la consulta es correcta. :) – leppie

+0

oh, no lo sabía. Interesante. – Femaref

+0

@leppie en realidad la consulta no funcionará como se esperaba. Al final del bloque de código OP, los valores no se cambian. Pero sí, la declaración en el PO se compilará/ejecutará. – Rangoric

2

No hay nada malo en ello per se, excepto que necesita para recorrer alguna manera, como llamar Count() en él.

Desde una perspectiva de 'estilo' no es bueno. Uno no esperaría que un iterador muteara un valor/propiedad de lista.

OMI lo siguiente sería mejor:

foreach (var x in fResults.Where(flight => flight.NonStop)) 
{ 
    x.Description = "Fly Direct!"; 
} 

La intención es mucho más claro para el lector o personal de mantenimiento del código.

2

Debe romper ese en dos bloques de código, uno para la recuperación y otro para establecer el valor:

var nonStopFlights = fResults.Where(f => f.NonStop); 

foreach(var flight in nonStopFlights) 
    flight.Description = "Fly Direct!"; 

O, si realmente odias el aspecto de foreach puede probar:

var nonStopFlights = fResults.Where(f => f.NonStop).ToList(); 

// ForEach is a method on List that is acceptable to make modifications inside. 
nonStopFlights.ForEach(f => f.Description = "Fly Direct!"); 
+0

+1 Estaba a punto de publicar una respuesta sobre la pieza "Nunca cambiar lo que se necesita". – Rangoric

5

Un problema con este enfoque es que no funcionará en absoluto. La consulta es floja, lo que significa que no ejecutará el código en el Seleccionar hasta que realmente lea algo de la consulta, y usted nunca hará eso.

Puede dar la vuelta al agregar .ToList() al final de la consulta, pero el código sigue utilizando efectos secundarios y descartando el resultado real. Debe utilizar el resultado de hacer la actualización en su lugar:

//Set all non-stop flights description 
foreach (var flight in fResults.Where(flight => flight.NonStop)) { 
    flight.Description = "Fly Direct!"; 
} 
+0

Sí, me doy cuenta de eso. ¿Puedo utilizar .Seleccione (...) como en el ejemplo y .ToList() justo antes de mostrar los resultados? – Pierre

+0

@Pierre: no necesita 'ToList' en absoluto si solo desea mostrar un resultado. El resultado del código en su pregunta es bastante inútil para mostrar, ya que es solo una lista de cadenas que son todas iguales. – Guffa

Cuestiones relacionadas