2012-07-19 18 views
10

Me he encontrado con algunos casos de errores Collection was modified; enumeration operation may not execute al devolver los resultados de una consulta LINQ en una función, como esta ... (Debo añadir que la función actúa como una implementación de una interfaz y los resultados abandonan este módulo para ser utilizado en otro.)¿Debo siempre invocar .ToArray en los resultados de la consulta LINQ devueltos en una función?

Public Function GetTheFuzzyFuzzbuzzes() As IEnumerable(of FuzzBuzz) _ 
    Implements IFoo.GetTheFuzzyFuzzBuzzes 

    Return mySecretDataSource.Where(Function(x) x.IsFuzzy) 
End Function 

¿Debo, por regla general, siempre llame .ToArray al devolver el resultado de la consulta LINQ en una función o propiedad getter si los datos subyacente tiene potencial para ser cambiado? Sé que hay un poco de eficiencia al hacer esto, pero tengo la sensación de que es seguro lo que hay que hacer y, por lo tanto, siempre se debe hacer para evitar problemas de acoplamiento temporal.

Editar:

Déjame hacer un mejor trabajo para explicar el dominio del problema.

Tenemos una implementación basada en gráficos de nuestra principal área de preocupación, que es un problema de optimización. Las entidades se representan como nodos gráficos. Los bordes ponderados con varios costos y otros parámetros expresan las relaciones entre los nodos. A medida que el usuario manipula los datos, creamos diferentes bordes y evaluamos las diversas opciones que pueden tomar contra el estado actual para darles retroalimentación sobre los resultados de cada opción. Los cambios realizados en los datos en el servidor por otros usuarios y programas se propagan inmediatamente al cliente a través de la tecnología de inserción. Usamos muchos subprocesos ...

... todo esto significa que tenemos muchas cosas sucediendo de forma muy asincrónica.

Nuestro programa se divide en módulos (basados ​​en el principio de responsabilidad única) con un proyecto de contrato y un proyecto de implementación resuelto en tiempo de ejecución, lo que significa que dependemos en gran medida de las interfaces. Normalmente pasamos los datos entre los módulos usando IEnumerable (ya que son kind-of-sort-of inmutables).

+1

¿podría publicar una versión pequeña y compilable de un programa que reproduzca esto? –

+0

Desafortunadamente, nuestra base de código es como 50000+ LOC con 68 proyectos, por lo que producir un buen ejemplo será un poco difícil. Vea el detalle expandido en la pregunta. –

Respuesta

5

No, no me gustaría hacer una regla de esto.

Entiendo su preocupación. El lado que llama puede no ser consciente de que sus acciones afectan los resultados de la consulta.

Hay algunos casos en los que realmente no puede hacer esto:

  • Hay ejemplos en los que al hacerlo podría causar un fuera de la memoria, como con enumerables infinitas, o en enumerador que produce una nueva imagen calculada en cada iteración. (Tengo ambos).
  • Si usa Any() o First() en sus consultas. Ambos requieren solo leer el primer elemento. El resto del trabajo se hace en vano.
  • Si espera que los Enumerables se encadenen con tuberías/filtros. Materializar los resultados intermedios es solo un costo extra.

Por otro lado, en muchos casos es más seguro materializar la consulta en una matriz cuando es concebible que el uso de la matriz tenga efectos secundarios que afectarán la consulta.

Al escribir software suena atractivo tener reglas que dicen "Cuando debe elegir entre X e Y, siempre haga X". No creo que haya tales reglas. Quizás en un 15% deberías hacer X, en un 5% definitivamente necesitas hacer Y, y para el resto de los casos, simplemente no importa.

Para aquellos que permanecen en el 80%, no hacer nada podría ser lo apropiado. Si inserta ToArray() en todas partes, el código sugiere erróneamente que hubo una razón por la que se hace esto.

+0

+1 para "el código sugiere erróneamente que hubo una razón por la cual se hace esto".Cuando se escribe una línea de código, nadie se sentirá seguro para eliminarla. A menudo resulta en código de espagueti a medida que pasa el tiempo. – Johnny5

2

Si va a devolver un IEnumerable (o IQueryable, o algo así como los que no son autónomos), las restricciones sobre cuándo se puede llamar, qué se puede hacer con él o durante cuánto tiempo puede hacerlo aferrarse a la necesidad de ser claramente deletreado.

Por esas razones, recomendaría devolver FuzzBuzz[] en lugar de IEnumerable<FuzzBuzz> si se trata de una API de algún tipo (es decir, entre capas). Si esto es parte de la implementación interna de una clase/módulo, es más fácil justificar una demora evaluada IEnumerable<FuzzBuzz>, pero aún es razonable usar la matriz.

A menos que el número de resultados sea grande o se llame con frecuencia, es poco probable que sea un problema de rendimiento (en muchos casos, el tiempo de CPU es barato y la memoria asignada al arreglo no se mantendrá durante mucho tiempo)

+0

Debe mencionarse que, en algunas situaciones (como la implementación de Linq de NHibernate), la conversión implícita de IQueryable a IEnumerable hará que se ejecute la consulta. – rossisdead

+0

Me gusta la mención de que el tipo de devolución debe ser una decisión de la API. Creo que hay demasiados de nosotros que no podemos pensar en la API que estamos infligiendo a nuestros compañeros. –

5

En general, no debe siempre llamar .ToArray o .ToList al devolver el resultado de la consulta LINQ.

Tanto .ToArray como .ToList son operaciones "codiciosas" (opuestas a la pereza) que realmente realizan consultas al origen de sus datos. Y el lugar y el momento adecuados para llamarlos son decisiones de arquitectura. Por ejemplo, podría establecer una regla en su proyecto para materializar todas las consultas de linq dentro de la Capa de acceso a datos, y así manejar allí todas las excepciones de la capa de datos. O para que no se ejecuten mientras sea posible y solo se obtienen los datos necesarios al final. Y hay muchos otros detalles relacionados con este tema.

Pero para llamar, o no llamar al .ToArray al devolver el resultado de su función, esta no es una pregunta y no tiene respuesta hasta que presente una muestra más detallada.

+0

Agregué más detalles a la pregunta (aunque no un ejemplo de código detallado), si desea actualizar su respuesta ... –

+0

El punto clave es _ nuestro programa está dividido en módulos (basado en el ** principio de responsabilidad única * *) _ - ¿le gustaría no mezclar y cómo va a dividir _data access_ y _graph la implementación de nuestra principal área de preocupación_? Y esta respuesta es individual para cada proyecto – Akim

2

"Como regla general", No, no siempre debe llamar a ToList/ToArray. De lo contrario, las consultas como myData.GetSomeSubset().WhereOtherCondition().Join(otherdata) invierten mucho tiempo asignando búferes temporales para cada llamada encadenada. Pero LINQ funciona mejor con colecciones inmutables. Es posible que desee tener más cuidado en el punto donde modifique mySecretDataSource.

En concreto, si su código siempre se estructura en torno a la modificación frecuente de su fuente de datos, que suena como una buena razón para volver con impaciencia una matriz en lugar de un IEnumerable

Cuestiones relacionadas