Comprobando si una lista está vacía con LINQ

122

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

En este momento 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 volverá con 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 conteo.

Dicho esto, ¿te parece legible la segunda opción? ¿Cual preferirías? ¿O puedes pensar en una mejor manera de probar una lista vacía?

La respuesta de Edit @ lassevk parece ser la más lógica, junto con un poco de verificación de 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();
}
Matt Hamilton
fuente
55
Mucho mejor, no mezcle isy castuse asy nullverifique:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev
2
¿Por qué escribir un método extra? ¿No es no es list.Any()equivalente a list.IsEmpty? El método del marco debe optimizarse: vale la pena escribir uno nuevo solo si descubriste que es un cuello de botella de perf.
dbkk
66
¿Alguien se molestó en medir el rendimiento en sus implementaciones sugeridas o todos están desechando ideas?
Michael Brown
Sugerí un problema a la biblioteca de clases de .NET Core que agrega un IsEmptymétodo de extensión. github.com/dotnet/corefx/issues/35054 Verifique y vote si lo desea y acepta.
RyotaMurohoshi

Respuestas:

100

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 básicos y simplemente usar la propiedad .Count de esos, en lugar del enfoque .Any (), pero luego recurrir a .Any () si no se puede garantizar.

Lasse V. Karlsen
fuente
44
¿O usa una línea y devuelve (fuente == nulo)? verdadero:! source.Any (); (Si no está lanzando una excepción)
Gage
1
Yo diría que sí, arroje una excepción para nulo, pero luego agregue un segundo método de extensión llamado IsNullOrEmpty().
devuxer
1
public static Boolean IsNullOrEmpty <T> (esta fuente IEnumerable <T>) {return source == null || ! source.Any (); }
dan
1
@Gage hoy en día:return !source?.Any() ?? true;
ricksmt
@ricksmt ¡Gracias por la actualización! Definitivamente voy a usar eso!
Gage
14

Haría una pequeña adición al código en el que parece haberse decidido: compruebe también ICollection, ya que esto también se implementa incluso en algunas clases genéricas no obsoletas (es decir, Queue<T>y Stack<T>). También lo usaría en aslugar de isporque 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();
}
Dan Tao
fuente
1
Me gusta esta respuesta Una advertencia es que algunas colecciones arrojarán excepciones cuando no implementen completamente una interfaz como la NotSupportedExceptiono NotImplementedException. La primera vez que usé su código de ejemplo cuando descubrí que una colección que estaba usando arrojó una excepción para Count (quién sabía ...).
Sam
1
Entiendo por qué tal optimización es útil para métodos como Count () que necesita enumerar todos los elementos. Pero Any () solo necesita enumerar como máximo un elemento, por lo que no veo el punto aquí. Por otro lado, los casts y las declaraciones if que está agregando son un costo fijo que debe pagar en cada llamada.
codymanix
8

LINQ en sí mismo debe estar haciendo una optimización seria alrededor del método Count () de alguna manera.

¿Te sorprende esto? Me imagino que para IListimplementaciones, Countsimplemente lee el número de elementos directamente mientras Anytiene que consultar el IEnumerable.GetEnumeratormétodo, crear una instancia y llamar MoveNextal menos una vez.

/ EDITAR @Matt:

Solo 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, usa en ICollectionlugar de IListpero el resultado es el mismo.

Konrad Rudolph
fuente
6

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 :)

Intentar la prueba del cronómetro nuevamente con una pila o matriz u otros escenarios realmente depende del tipo de lista que parece, porque demuestran que Count es más lento.

¡Entonces 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 recuento fue aún más rápido, al contrario que otros tipos)

crisol
fuente
12
Enumerable.Count<T>()Tiene un manejo especial para ICollection<T>. Si intenta esto con algo más que una lista básica, espero que vea resultados significativamente diferentes (más lentos). Any()Sin embargo, seguirá siendo el mismo.
Marc Gravell
2
Tengo que estar de acuerdo con Marc; Esta no es una prueba realmente justa.
Dan Tao el
¿Alguna idea de por qué no hay un manejo especial Enumerable.Any<T>()para ICollection<T>? ¿Seguramente el sin parámetros también Any()podría verificar la Countpropiedad ICollection<T>?
Lukazoid
5

List.Countes O (1) de acuerdo con la documentación de Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

así que solo utilízalo 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 lo llama List.Countno tiene que recorrer cada elemento para obtenerlo, solo devuelve el miembro de datos.

Dasmowenator
fuente
1
si es un "IEnumerable" entonces no. (para empezar, IEnumerable no tiene una propiedad "Count", tiene un método Count ()). Llamar a "Count ()" requerirá que IEnumerable examine cada elemento de la lista. Mientras que "Any" simplemente regresará tan pronto como encuentre 1 elemento.
00jt
Depende de la fuente de datos. Si usa el rendimiento para construir un IEnumerable, tendrá que atravesar el IEnumerable para saber su tamaño. Por lo tanto, es solo O (1) en algunos casos. No siempre es O (1).
TamusJRoyce
3

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

  • Any() vuelve tan pronto como se encuentre 1 artículo.
  • Count() tiene que seguir revisando toda la lista.

Por ejemplo, suponga que la enumeración tenía 1000 artículos.

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

Esto es potencialmente peor si usa 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, use la propiedad Count de esa lista.

Keith
fuente
Las diferencias entre Any () y Count () parecen claras, pero el código de creación de perfiles de @ crucible parece indicar que Count () es más rápido para ciertas implementaciones de IEnumerable <T>. Para la Lista <T> no puedo hacer que Any () dé un resultado más rápido que Count () hasta que el tamaño de la lista aumente en los miles de elementos. LINQ en sí mismo debe estar haciendo una optimización seria alrededor del método Count () de alguna manera.
Matt Hamilton
3

@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 optimizarla llamando al método de extensión Count () IList<T>.

Solo 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 de tiempo de ejecución para el caso especial de IList<T>.

/ EDIT @Konrad +1 mate: es probable que tengas razón al respecto ICollection<T>.

Matt Hamilton
fuente
1

Ok, ¿y qué hay de 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 bosquejado esta solución. Se mencionó que el método Any () hará esto, pero ¿por qué no hacerlo usted mismo? Saludos

Jonny Dee
fuente
3
PERO se vuelve menos sucinto cuando lo encerras correctamente en un usingbloque, ya que de lo contrario construiste un IDisposableobjeto y luego lo abandonaste. Luego, por supuesto, se vuelve más sucinto cuando utiliza el método de extensión que ya existe y simplemente lo cambia a return !enumerable.Any()(que hace precisamente esto).
Dan Tao el
¿Por qué reescribir un método ya existente? Como se mencionó, Any()realiza exactamente eso, por lo que agregar exactamente el mismo método con otro nombre será confuso.
Julien N
1

Otra idea:

if(enumerable.FirstOrDefault() != null)

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

ChulioMartinez
fuente
3
¿Qué sucede si tiene una lista no vacía en la que el primer elemento es nulo?
Ekevoo
1

Esto fue crítico para que esto funcionara con Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}
Holt Mansfield
fuente
¿Cómo responde eso a la pregunta? la colección no puede ser nula mientras no tenga elementos dentro de ella.
Martin Verjans
0

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

antes de

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)
{
    //....
}
Gandarez
fuente
0
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);
    }
suneelsarraf
fuente
0

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;
}
devuxer
fuente
-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;
Milad Sadeghi
fuente
-3

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

usuario3149517
fuente
1
Esta es una idea horrible. ToList () no debe ser usado en exceso, ya que obliga a los enumerables a ser completamente evaluados. Utilice .Any () en su lugar.
Jon Rea
-5

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;
    }
}
Jonny Dee
fuente
55
Evite tal uso de excepciones. En el código anterior, espera una excepción para ciertas entradas bien definidas (es decir, enumeraciones vacías). Por lo tanto, no son excepciones, son la regla. Es un abuso de este mecanismo de control que tiene implicaciones en la legibilidad y el rendimiento. Reserve el uso de excepciones para casos verdaderamente excepcionales.
Konrad Rudolph el
En general, estaría de acuerdo. Pero esta es una solución para un método IsEmpty faltante correspondiente. Y diría que una solución alternativa nunca es la forma ideal de hacer algo ... Además, especialmente en este caso, la intención es muy clara y el código "sucio" está encapsulado y escondido en un lugar bien definido.
Jonny Dee el
3
-1: Si quieres hacerlo de esta manera, usa FirstOrDefault (), como en la respuesta de ChulioMartinez.
Daniel Rose
3
El manejo de excepciones tiene una eficiencia de rendimiento realmente pobre. Entonces esta puede ser la peor solución aquí.
Julien N
"Las excepciones deben ser excepcionales". - no los use para el flujo normal del programa.
Jon Rea