Manejo de advertencia para posible enumeración múltiple de IEnumerable

En mi código necesito usar un IEnumerable varias veces para obtener el error de IEnumerable de “Posible enumeración múltiple de IEnumerable “.

Código de muestra:

 public List Foo(IEnumerable objects) { if (objects == null || !objects.Any()) throw new ArgumentException(); var firstObject = objects.First(); var list = DoSomeThing(firstObject); var secondList = DoSomeThingElse(objects); list.AddRange(secondList); return list; } 
  • Puedo cambiar el parámetro objects para que sea List y luego evitar la posible enumeración múltiple, pero luego no obtengo el objeto más alto que puedo manejar.
  • Otra cosa que puedo hacer es convertir IEnumerable en List al comienzo del método:

  public List Foo(IEnumerable objects) { var objectList = objects.ToList(); // ... } 

Pero esto es simplemente incómodo .

¿Qué haría usted en este escenario?

El problema con tomar IEnumerable como parámetro es que le dice a las personas que llaman “Deseo enumerar esto”. No les dice cuántas veces desea enumerar.

Puedo cambiar el parámetro objects para que sea List y luego evitar la posible enumeración múltiple, pero luego no obtengo el objeto más alto que puedo manejar .

El objective de tomar el objeto más alto es noble, pero deja espacio para demasiadas suposiciones. ¿Realmente desea que alguien pase una consulta de LINQ a SQL a este método, solo para que lo enumere dos veces (obteniendo resultados potencialmente diferentes cada vez?)

La falta semántica aquí es que una persona que llama, que tal vez no se tome el tiempo para leer los detalles del método, puede suponer que solo itera una vez, por lo que le pasan un objeto caro. Su firma de método no indica de ninguna manera.

Al cambiar la firma del método a IList / ICollection , al menos le dejará más claro a la persona que llama cuáles son sus expectativas, y puede evitar errores costosos.

De lo contrario, la mayoría de los desarrolladores que buscan el método podrían suponer que solo iterarás una vez. Si tomar un IEnumerable es tan importante, debe considerar hacer .ToList() al comienzo del método.

Es una pena .NET no tiene una interfaz que sea IEnumerable + Count + Indexer, sin métodos de Agregar / Eliminar, que es lo que sospecho que resolvería este problema.

Si sus datos siempre serán repetibles, quizás no se preocupe. Sin embargo, también puede desenrollarlo; esto es especialmente útil si los datos entrantes pueden ser grandes (por ejemplo, leer desde un disco / red):

 if(objects == null) throw new ArgumentException(); using(var iter = objects.GetEnumerator()) { if(!iter.MoveNext()) throw new ArgumentException(); var firstObject = iter.Current; var list = DoSomeThing(firstObject); while(iter.MoveNext()) { list.Add(DoSomeThingElse(iter.Current)); } return list; } 

Tenga en cuenta que cambié un poco la semántica de DoSomethingElse, pero esto es principalmente para mostrar el uso desenrollado. Podría volver a envolver el iterador, por ejemplo. También podrías convertirlo en un bloque iterador, lo que podría ser bueno; entonces no hay una list , y usted yield return los artículos a medida que los obtiene, en lugar de agregarlos a una lista para ser devueltos.

Si el objective es realmente evitar enumeraciones múltiples que la respuesta de Marc Gravell es la de leer, pero manteniendo la misma semántica, puede eliminar simplemente las llamadas Any y First redundantes e ir con:

 public List Foo(IEnumerable objects) { if (objects == null) throw new ArgumentNullException("objects"); var first = objects.FirstOrDefault(); if (first == null) throw new ArgumentException( "Empty enumerable not supported.", "objects"); var list = DoSomeThing(first); var secondList = DoSomeThingElse(objects); list.AddRange(secondList); return list; } 

Tenga en cuenta que esto supone que IEnumerable no es genérico o al menos está restringido a ser un tipo de referencia.

Normalmente sobrecarga mi método con IEnumerable e IList en esta situación.

 public static IEnumerable Method( this IList source ){... } public static IEnumerable Method( this IEnumerable source ) { /*input checks on source parameter here*/ return Method( source.ToList() ); } 

Me ocupo de explicar en los comentarios resumidos de los métodos que llamar a IEnumerable realizará un .ToList ().

El progtwigdor puede elegir .ToList () en un nivel superior si se concatenan múltiples operaciones y luego llama a la sobrecarga IList o deja que mi sobrecarga IEnumerable se encargue de eso.

Utilizar IReadOnlyCollection o IReadOnlyList en la firma del método en lugar de IEnumerable , tiene la ventaja de hacer explícito que puede ser necesario verificar el conteo antes de iterar, o repetir varias veces por algún otro motivo.

Sin embargo, tienen un gran inconveniente que causará problemas si intenta refactorizar su código para usar interfaces, por ejemplo para hacerlo más comprobable y amigable con el proxy dynamic. El punto clave es que IList no hereda de IReadOnlyList , y de manera similar para otras colecciones y sus respectivas interfaces de solo lectura. (En resumen, esto se debe a que .NET 4.5 quería mantener la compatibilidad ABI con versiones anteriores. Pero ni siquiera aprovecharon la oportunidad para cambiar eso en el núcleo de .NET ) .

Esto significa que si obtiene un IList de alguna parte del progtwig y desea pasarlo a otra parte que espera una IReadOnlyList , ¡no puede! Sin embargo, puede pasar un IList como IEnumerable .

Al final, IEnumerable es la única interfaz de solo lectura admitida por todas las colecciones .NET, incluidas todas las interfaces de colección. Cualquier otra alternativa volverá para morderte al darte cuenta de que te encerraste en algunas elecciones de architecture. Así que creo que es el tipo adecuado para usar en las firmas de funciones para express que solo quieres una colección de solo lectura.

(Tenga en cuenta que siempre puede escribir un método de extensión IReadOnlyList ToReadOnly(this IList list) que arroja simples si el tipo subyacente admite ambas interfaces, pero debe agregarlo manualmente en todas partes al refactorizar, donde IEnumerable siempre es compatible).

Como siempre, esto no es absoluto, si escribe un código pesado en la base de datos donde la enumeración múltiple accidental sería un desastre, es posible que prefiera una compensación diferente.

En primer lugar, esa advertencia no siempre significa mucho. Normalmente lo desactivo después de asegurarme de que no sea un cuello de botella de rendimiento. Simplemente significa que IEnumerable se evalúa dos veces, lo que generalmente no es un problema a menos que la evaluationIEnumerable mucho tiempo. Incluso si lleva mucho tiempo, en este caso solo utiliza un elemento la primera vez.

En este escenario, también podría aprovechar aún más los poderosos métodos de extensión de linq.

 var firstObject = objects.First(); return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList(); 

Es posible evaluar solo el IEnumerable una vez en este caso con cierta molestia, pero primero IEnumerable perfil y vea si realmente es un problema.