2010-03-26 36 views

Respuesta

71

Llamando a Contar en IEnumerable<T> Supongo que se refiere al método de extensión Count en System.Linq.Enumerable. Length no es un método en IEnumerable<T> sino más bien una propiedad en tipos de matriz en .Net como int[].

La diferencia es el rendimiento. Se garantiza que la propiedad Length es una operación O (1). La complejidad del método de extensión Count difiere según el tipo de tiempo de ejecución del objeto. Intentará convertir a varios tipos que admitan O (1) búsqueda de longitud como ICollection<T> a través de una propiedad Count. Si no hay ninguno disponible, enumerará todos los elementos y los contará que tienen una complejidad de O (N).

Por ejemplo

int[] list = CreateSomeList(); 
Console.WriteLine(list.Length); // O(1) 
IEnumerable<int> e1 = list; 
Console.WriteLine(e1.Count()); // O(1) 
IEnumerable<int> e2 = list.Where(x => x <> 42); 
Console.WriteLine(e2.Count()); // O(N) 

El valor e2 se implementa como un C# iterador que no soporta O (1) de recuento y por lo tanto el método Count debe enumerar toda la colección para determinar cuánto tiempo es.

+2

'List ' no tiene una propiedad Length: tiene una propiedad Count. Las matrices tienen una "Longitud" sin embargo. 'Count' se especifica en' ICollection' y 'ICollection ' (que se extiende 'IList '). –

+0

@Jon, doh. Achacando la culpa a la falta de sueño aquí. Actualizará – JaredPar

+4

Y si su 'IEnumerable ' es infinitamente largo, 'Count()' nunca volverá ... –

18

Poco añadido al comentario de Jon Skeet.

Hay código fuente del método Count() extensión:

.NET 3:

public static int Count<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    ICollection<TSource> is2 = source as ICollection<TSource>; 
    if (is2 != null) 
    { 
     return is2.Count; 
    } 
    int num = 0; 
    using (IEnumerator<TSource> enumerator = source.GetEnumerator()) 
    { 
     while (enumerator.MoveNext()) 
     { 
      num++; 
     } 
    } 
    return num; 
} 

.NET 4:

public static int Count<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    ICollection<TSource> is2 = source as ICollection<TSource>; 
    if (is2 != null) 
    { 
     return is2.Count; 
    } 
    ICollection is3 = source as ICollection; 
    if (is3 != null) 
    { 
     return is3.Count; 
    } 
    int num = 0; 
    using (IEnumerator<TSource> enumerator = source.GetEnumerator()) 
    { 
     while (enumerator.MoveNext()) 
     { 
      num++; 
     } 
    } 
    return num; 
} 
+6

Tenga en cuenta que en .NET 4 hay otro bloque para verificar también el tipo 'ICollection' no genérico. (Como eso también tiene una propiedad 'Count') –

+0

@Jon Skeet: gracias – bniwredyc

+0

¿Alguien sabe qué 'usar' para obtener la clase' Error' que utiliza este método? Parece que no puedo encontrarlo en ninguna parte de MSDN, excepto en la documentación de JScript. –

1
  • la longitud es una propiedad fija, por ejemplo, de una única matriz o serie dimensional. Por lo tanto, nunca es necesaria una operación de recuento (las matrices multidimensionales tienen un tamaño de todas las dimensiones multiplicado). La operación O (1) aquí significa que el tiempo de recuperación es siempre el mismo, sin importar cuántos elementos haya. Una búsqueda lineal sería (opuesta a esto) O (n).

  • La propiedad Count en ICollections (Lista y la Lista <T>, por ejemplo) puede cambiar, por lo que o bien ha de ser actualizado en Add/Remove operaciones, o cuando se solicita conde después de la Colección ha cambiado. Depende de la implementación del objeto.

  • El método Count() de LINQ básicamente itera CADA VEZ que se llama (excepto cuando el objeto es un tipo ICollection, entonces se solicita la propiedad ICollection.Count).

Tenga en cuenta que a menudo no son IEnumerables ya definido colecciones de objetos (como listas, matrices, tablas hash, etc.), pero enlace a operaciones en segundo plano, que generan resultados siempre que sean prescritas (llamado ejecución diferida).

Por lo general, tiene un SQL como declaración de LINQ como esto (la aplicación típica de ejecución diferida):

IEnumerable<Person> deptLeaders = 
    from p in persons 
    join d in departments 
     on p.ID equals d.LeaderID 
    orderby p.LastName, p.FirstName 
    select p; 

Entonces, hay un código como éste:

if (deptLeaders.Count() > 0) 
{ 
    ReportNumberOfDeptLeaders(deptLeaders.Count()); 
    if (deptLeaders.Count() > 20) 
     WarnTooManyDepartmentLeaders(deptLeaders.Count()); 
} 

lo tanto, cuando una advertencia dado que se emiten demasiados Líderes de Departamento, .NET pasa CUATRO VECES a través de las personas, los compara con los líderes del departamento, los ordena por su nombre y luego cuenta los objetos resultantes.

Y esto es solo cuando las personas y los departamentos son conjuntos de valores preestablecidos, no las consultas mismas.

+0

Podría agregar que '.Count()> 0' es lo mismo que' .Any() '. – jedmao

+1

@sfjedi: Creo que no es lo mismo. Any() se detiene cuando se ha encontrado un elemento, mientras que Count() lo recorre todo. Por lo tanto, cuando se tiene un IEnumerable, posible para la ejecución diferida, Any() se debe preferir para la verificación vacía. –

+3

¿No sería '.Any()' más eficiente que '.Count()> 0' entonces? Por cierto, Resharper siempre se queja de '.Count()> 0'. Es por eso que lo menciono con confianza. – jedmao

Cuestiones relacionadas