Pregunta

¿Cuál es la "mejor" forma (teniendo en cuenta tanto la velocidad como la legibilidad) de determinar si una lista está vacía?Incluso si la lista es de tipo IEnumerable<T> y no tiene una propiedad Count.

Ahora mismo estoy dando vueltas entre esto:

if (myList.Count() == 0) { ... }

y esto:

if (!myList.Any()) { ... }

Supongo que la segunda opción es más rápida, ya que devolverá un resultado tan pronto como vea el primer elemento, mientras que la segunda opción (para un IEnumerable) necesitará visitar cada elemento para devolver el recuento.

Dicho esto, ¿la segunda opción le parece legible?¿Cual preferirías?¿O se te ocurre una forma mejor de comprobar si hay una lista vacía?

Editar La respuesta de @lassevk parece ser la más lógica, junto con un poco de verificación del tiempo de ejecución para usar un recuento en caché si es posible, como este:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}
¿Fue útil?

Solución

Podrías hacer esto:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

Editar:Tenga en cuenta que simplemente usar el método .Count será rápido si la fuente subyacente realmente tiene una propiedad Count rápida.Una optimización válida anterior sería detectar algunos tipos base y simplemente usar la propiedad .Count de ellos, en lugar del enfoque .Any(), pero luego recurrir a .Any() si no se puede ofrecer ninguna garantía.

Otros consejos

Haría una pequeña adición al código que parece haber elegido:comprobar también por ICollection, ya que esto también lo implementan incluso algunas clases genéricas no obsoletas (es decir, Queue<T> y Stack<T>).Yo también usaría as en lugar de is ya que es más idiomático y Se ha demostrado que es más rápido..

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}

El propio LINQ debe estar haciendo alguna optimización seria en torno al método Count() de alguna manera.

¿Esto te sorprende?Me imagino que para IList implementaciones, Count simplemente lee el número de elementos directamente mientras Any tiene que consultar el IEnumerable.GetEnumerator método, crear una instancia y llamar MoveNext al menos una vez.

/EDITAR @Matt:

Sólo puedo suponer que el método de extensión Count() para IEnumerable está haciendo algo como esto:

Sí, por supuesto que lo hace.Esto es lo que quise decir.En realidad, utiliza ICollection en lugar de IList Pero el resultado es el mismo.

Acabo de escribir una prueba rápida, prueba esto:

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

El segundo es casi tres veces más lento :)

Al volver a intentar la prueba del cronómetro con una pila o una matriz u otros escenarios, realmente depende del tipo de lista que parezca, porque demuestran que Count es más lento.

¡Supongo que depende del tipo de lista que estés usando!

(Solo para señalar, puse más de 2000 objetos en la Lista y el conteo fue aún más rápido, a diferencia de otros tipos)

List.Count es O(1) según la documentación de Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

así que solo usa List.Count == 0 es mucho más rápido que una consulta

Esto se debe a que tiene un miembro de datos llamado Count que se actualiza cada vez que se agrega o elimina algo de la lista, por lo que cuando llamas List.Count no tiene que recorrer cada elemento para obtenerlo, simplemente devuelve el miembro de datos.

La segunda opción es mucho más rápida si tienes varios elementos.

  • Any() devuelve tan pronto como se encuentra 1 artículo.
  • Count() Tiene que seguir repasando toda la lista.

Por ejemplo, supongamos que la enumeración tuviera 1000 elementos.

  • Any() Verificaría el primero y luego devolvería verdadero.
  • Count() devolvería 1000 después de recorrer toda la enumeración.

Esto es potencialmente peor si utiliza una de las anulaciones de predicados: Count() todavía tiene que verificar cada elemento, incluso si solo hay una coincidencia.

Te acostumbras a usar Cualquiera: tiene sentido y es legible.

Una advertencia: si tiene una Lista, en lugar de solo un IEnumerable, utilice la propiedad Count de esa lista.

@Konrad lo que me sorprende es que en mis pruebas paso la lista a un método que acepta IEnumerable<T>, por lo que el tiempo de ejecución no puede optimizarlo llamando al método de extensión Count() para IList<T>.

Sólo puedo suponer que el método de extensión Count() para IEnumerable está haciendo algo como esto:

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

...en otras palabras, un poco de optimización del tiempo de ejecución para el caso especial de IList<T>.

/EDITAR @Konrad +1 compañero: tienes razón en que es más probable que estés en ICollection<T>.

Bien, ¿qué pasa con este?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

EDITAR:Me acabo de dar cuenta de que alguien ya ha esbozado esta solución.Se mencionó que el método Any() hará esto, pero ¿por qué no hacerlo usted mismo?Saludos

Otra idea:

if(enumerable.FirstOrDefault() != null)

Sin embargo, me gusta más el enfoque Any().

Esto fue fundamental para que esto funcionara con Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

Si verifico con Count() Linq ejecuta un "SELECT COUNT(*).." en la base de datos, pero necesito verificar si los resultados contienen datos, resolví introducir FirstOrDefault() en lugar de Count();

Antes

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

Después

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

Aquí está mi implementación de la respuesta de Dan Tao, permitiendo un predicado:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

myList.ToList().Count == 0.Eso es todo

Este método de extensión funciona para mí:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top