2008-09-03 23 views
121

¿Cuál es la forma "mejor" (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.Comprobando si una lista está vacía con LINQ

En este momento estoy lanzando entre el presente:

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

y esto:

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

Mi conjetura es que la segunda opción es más rápida, ya que va a volver con un resultado tan pronto como vea el primer elemento, mientras que la segunda opción (para un Enumerable) deberá visitar cada elemento para devolver el conteo.

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

Editar @ respuesta de lassevk parece ser la más lógica, junto con un poco de tiempo de ejecución de la comprobación de usar un recuento almacenado en caché si es posible, de esta manera:

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

    return !list.Any(); 
} 
+5

Mucho mejor, no mezcle 'is' y' cast', pero use 'as' y' null' check: 'ICollection collection = list as ICollection ; if (collection! = null) return colllection.Count; ' – abatishchev

+2

¿Por qué escribir un método extra? Is not is not 'list.Any()' equivalent to 'list.IsEmpty'? El método de marco debe ser optimizado; vale la pena escribir uno nuevo solo si descubrió que es un cuello de botella perf. – dbkk

+6

¿Alguien se molestó en medir el rendimiento en sus implementaciones sugeridas o todos están desperdiciando ideas? –

Respuesta

93

usted puede 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 utilizando el método .Count será rápido si la fuente subyacente en realidad tiene una propiedad de cuenta rápida. Una optimización válida anterior sería detectar algunos tipos básicos y simplemente usar la propiedad .Count de aquellos, en lugar del método .Any(), pero luego volver a .Any() si no se puede garantizar.

+4

O use una línea y devuelva (fuente == nulo)? true:! source.Any(); (Si no estás lanzando una excepción) – Gage

+1

Diría que sí, lanza una excepción para null, pero luego agrega un segundo método de extensión llamado 'IsNullOrEmpty()'. – devuxer

+1

public static Boolean IsNullOrEmpty (esta fuente IEnumerable ) { return source == null || ! source.Any(); } – dan

6

que acabo de escribir una prueba rápida, intente 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 :)

Tratar la prueba cronómetro de nuevo con una pila o una matriz o realmente otros escenarios de TI depende del tipo de lista que parezca, porque demuestran que Count es más lento.

¡Así que supongo que depende del tipo de lista que estés usando!

(Sólo para señalar, puse 2000 + objetos de la lista y el recuento fue todavía más rápido, frente a otros tipos)

+10

'Enumerable.Count ()' tiene un manejo especial para 'ICollection '. Si prueba esto con algo * otro * que no sea una lista básica, espero que vea * significativamente * resultados diferentes (más lentos). 'Any()' seguirá siendo el mismo, sin embargo. –

+2

Tengo que estar de acuerdo con Marc; esta no es una prueba realmente justa. –

+0

Cualquier idea de por qué no hay un manejo especial para 'Enumerable.Any ()' para 'ICollection '? seguramente el parámetro 'Any()' podría simplemente verificar la propiedad 'Count' para' ICollection 'también? – Lukazoid

2

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

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

Por ejemplo, supongamos que la enumeración tiene 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 invalidaciones de predicados - Count() todavía tiene que verificar cada elemento, incluso si solo hay una coincidencia.

Te acostumbras a utilizar Cualquiera, tiene sentido y es legible.

Una advertencia: si tiene una Lista, en lugar de solo un Enumerable, use la propiedad Cuenta de esa lista.

+0

Las diferencias entre Any() y Count() parecen claras, pero el código de perfil de @ crisol parece indicar que Count() es más rápido para ciertas implementaciones de IEnumerable . Para la Lista no puedo obtener Any() para dar un resultado más rápido que Count() hasta que el tamaño de la lista esté arriba en los miles de elementos. LINQ en sí mismo debe estar haciendo una optimización seria alrededor del método Count() de alguna manera. –

8

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

¿Le sorprende? Imagino que para las implementaciones IList, Count simplemente lee el número de elementos directamente, mientras que Any tiene que consultar el método IEnumerable.GetEnumerator, crea una instancia y llama al MoveNext al menos una vez.

/EDIT @ Matt:

sólo puedo asumir 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 ICollection en lugar de IList pero el resultado es el mismo.

3

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

que sólo puede 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 en tiempo de ejecución para el caso especial de IList<T>.

/EDIT @Konrad +1 amigo - tiene razón al respecto más probablemente en ICollection<T>.

-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; 
    } 
} 
+5

Evite el 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. Eso 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. –

+0

En general, estoy de acuerdo. Pero esta es una solución alternativa para un método IsEmpty faltante correspondiente. Y yo diría que una solución alternativa nunca es la manera 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. –

+3

-1: si quieres hacerlo de esta manera, usa FirstOrDefault(), como en la respuesta de ChulioMartinez. –

1

Ok, así que ¿qué tal este?

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

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

+3

PERO se vuelve menos conciso cuando lo encierras apropiadamente en un bloque 'using' ya que de lo contrario has construido un objeto' IDisposable' y luego lo abandonaste. Entonces, por supuesto, se vuelve * más * sucinto cuando utilizas el método de extensión que ya está allí y simplemente lo cambias a 'return! Enumerable.Any()' (que hace precisamente esto). –

+0

¿Por qué reescribir un método ya existente? Como se menciona 'Any()' realiza exactamente eso, por lo que agregar exactamente el mismo método con otro nombre será confuso. –

1

Otra idea:

if(enumerable.FirstOrDefault() != null) 

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

+1

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

3

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

tan sólo tiene que utilizar List.Count == 0 es mucho más rápido que una consulta

Esto es porque tiene un miembro de datos llamado conde que se actualiza cualquier Cuando se agrega o elimina algo de la lista, cuando llame al List.Count no tiene que recorrer cada elemento para obtenerlo, simplemente devuelve el miembro de datos.

+1

si es un "IEnumerable" y luego no. (para los principiantes 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 "Cualquiera" simplemente regresará tan pronto como encuentre 1 elemento. – 00jt

+0

Depende de la fuente de datos. Si usa el rendimiento para construir un IEnumerable, deberá atravesar el IEnumerable para conocer su tamaño. Entonces es solo O (1) en algunos casos. No siempre es O (1). – TamusJRoyce

14

me gustaría hacer una pequeña adición al código que parece haberse asentado en: comprobar también para ICollection, ya que esto se lleva a cabo incluso por algunos clases genéricas no obsoletos, así (es decir, Queue<T> y Stack<T>). También usaría as en lugar de is ya que es más idiomático y has been shown to be faster.

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(); 
} 
+1

Me gusta esta respuesta. Una palabra de advertencia es que algunas colecciones generarán excepciones cuando no implementen por completo una interfaz como 'NotSupportedException' o' NotImplementedException'. Primero utilicé el ejemplo de código cuando descubrí que una colección que estaba usando arrojó una excepción para Count (quién sabía ...). – Sam

0

Si puedo comprobar con Count() LINQ ejecuta un "SELECT COUNT (*) .." en la base de datos, pero tengo que comprobar si los resultados contienen datos, decidí introducir FirstOrDefault() en lugar de Contar();

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) 
{ 
    //.... 
} 
1

Esto fue fundamental para conseguir que esto funcione con Entity Framework:

var genericCollection = list as ICollection<T>; 

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

¿Cómo responde eso la pregunta? la colección no puede ser nula sin tener elementos dentro. –

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

Al menos explique qué hace este código. – quantum

0

Aquí está mi aplicación de la respuesta de Dan Tao, lo que permite 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; 
} 
-3

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

+1

Esta es una idea horrible. ToList() no se debe usar en exceso, ya que obliga a que el enumerable se evalúe por completo. Use .Any() en su lugar. –

Cuestiones relacionadas