c# - que - resharper visual studio code
Advertencia de manipulación para una posible enumeración múltiple de IEnumerable (7)
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 de objetos para que sea Lista y luego evitar la posible enumeración múltiple, pero luego no obtengo el objeto más alto que puedo manejar .
El objetivo de tomar el objeto más alto es noble, pero deja espacio para demasiadas suposiciones. ¿Realmente desea que alguien pase una consulta LINQ to 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 quizás no se toma el tiempo para leer los detalles del método, puede asumir que solo se itera una vez, por lo que le pasa un objeto costoso. 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 pueden evitar errores costosos.
De lo contrario, la mayoría de los desarrolladores que observan el método pueden asumir que solo se itera una vez. Si tomar un IEnumerable
es tan importante, debería considerar hacer el .ToList()
al inicio del método.
Es una pena .NET no tiene una interfaz que sea IEnumerable + Count + Indexer, sin los métodos Add / Remove etc., que es lo que sospecho que solucionaría este problema.
En mi código necesito usar un IEnumerable<>
varias veces para obtener el error Resharper de "Posible enumeración múltiple de IEnumerable
".
Código de muestra:
public List<object> Foo(IEnumerable<object> 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 de
objects
para que seaList
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
aList
al comienzo del método:
public List<object> Foo(IEnumerable<object> objects)
{
var objectList = objects.ToList();
// ...
}
Pero esto es simplemente incómodo .
¿Qué haría usted en este escenario?
El uso de IReadOnlyCollection<T>
o IReadOnlyList<T>
en la firma del método en lugar de IEnumerable<T>
, tiene la ventaja de hacer explícito que es posible que deba verificar el conteo antes de iterar, o iterar varias veces por alguna otra razón.
Sin embargo, tienen un gran inconveniente que causará problemas si intenta refactorizar su código para usar interfaces, por ejemplo, para que sea más comprobable y amigable con el proxy dinámico. El punto clave es que IList<T>
no hereda de IReadOnlyList<T>
, 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 de ABI con versiones anteriores. Pero ni siquiera aprovecharon la oportunidad para cambiar eso en el núcleo de .NET ) .
Esto significa que si obtienes un IList<T>
de alguna parte del programa y quieres pasarlo a otra parte que espera un IReadOnlyList<T>
, ¡no puedes! Sin embargo, puede pasar un IList<T>
como un IEnumerable<T>
.
Al final, IEnumerable<T>
es la única interfaz de solo lectura compatible con todas las colecciones .NET, incluidas todas las interfaces de colección. Cualquier otra alternativa volverá a morderte cuando te des cuenta de que te has excluido de algunas opciones de arquitectura. Así que creo que es el tipo adecuado para usar en firmas de funciones para expresar que solo desea una colección de solo lectura.
(Tenga en cuenta que siempre puede escribir un método de extensión IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)
que se realiza de forma simple si el tipo subyacente admite ambas interfaces, pero debe agregarlo manualmente en todas partes al refactorizar, donde IEnumerable<T>
siempre es compatible.)
Como siempre, esto no es un absoluto, si está escribiendo código pesado en la base de datos en el que 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 tanto. Normalmente lo deshabilité después de asegurarme de que no fuera 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 evaluation
sí lleve mucho tiempo. Incluso si lleva mucho tiempo, en este caso solo utilizará un elemento la primera vez.
En este escenario, también podría explotar aún más los potentes métodos de extensión linq.
var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();
Es posible evaluar solo el IEnumerable
una vez en este caso con algunos problemas, pero primero IEnumerable
perfil y vea si realmente es un problema.
Normalmente sobrecargo mi método con IEnumerable e IList en esta situación.
public static IEnumerable<T> Method<T>( this IList<T> source ){... }
public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
/*input checks on source parameter here*/
return Method( source.ToList() );
}
Me ocupo de explicar en el resumen de comentarios de los métodos que al llamar a IEnumerable se realizará una .ToList ().
El programador puede elegir .ToList () en un nivel superior si se están concatenando múltiples operaciones y luego llamar a la sobrecarga de IList o dejar que mi sobrecarga de IEnumerable se encargue de eso.
Si el objetivo es realmente evitar múltiples enumeraciones, la respuesta de Marc Gravell es la que debe leerse, pero manteniendo la misma semántica, simplemente podría eliminar las llamadas redundantes de " First
y First
e ir con:
public List<object> Foo(IEnumerable<object> 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 para ser un tipo de referencia.
Si solo necesitas marcar el primer elemento, puedes verlo sin iterar toda la colección:
public List<object> Foo(IEnumerable<object> objects)
{
object firstObject;
if (objects == null || !TryPeek(ref objects, out firstObject))
throw new ArgumentException();
var list = DoSomeThing(firstObject);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
IEnumerator<T> enumerator = source.GetEnumerator();
if (!enumerator.MoveNext())
{
first = default(T);
source = Enumerable.Empty<T>();
return false;
}
first = enumerator.Current;
T firstElement = first;
source = Iterate();
return true;
IEnumerable<T> Iterate()
{
yield return firstElement;
using (enumerator)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
}
Si sus datos siempre serán repetibles, tal vez no se preocupe por eso. 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 de iteradores, lo que podría ser bueno; entonces no hay una list
, y usted yield return
los artículos a medida que los obtenga, en lugar de agregarlos a una lista que se devolverá.