¿Qué método funciona mejor: .Any () vs .Count ()> 0?

578

en el System.Linqespacio de nombres, ahora podemos extender nuestros IEnumerables para tener Any () y Count () métodos de extensión .

Recientemente me dijeron que si quiero verificar que una colección contenga 1 o más elementos dentro de ella, debería usar el .Any()método de extensión en lugar del método de .Count() > 0extensión porque el .Count()método de extensión tiene que recorrer en iteración todos los elementos.

En segundo lugar, algunas colecciones tienen una propiedad (no un método de extensión) que es Counto Length. ¿Sería mejor usarlos, en lugar de .Any()o .Count()?

si / nae?

Pure.Krome
fuente
Es mejor usar Any () en Enumerables y Count on Collections. Si alguien siente que escribir '(somecollection.Count> 0)' confundirá o causará problemas de legibilidad, mejor escríbalo como un método de extensión llamado Any (). Entonces todos satisfechos. En cuanto al rendimiento y la legibilidad. Para que todo su código tenga consistencia y el desarrollador individual en su proyecto no tenga que preocuparse por elegir Count vs Any.
Mahesh Bongani

Respuestas:

710

Si usted está comenzando con algo que tiene una .Lengtho .Count(como por ejemplo ICollection<T>, IList<T>, List<T>, etc.) - entonces esto va a ser la opción más rápida, ya que no tiene que pasar por la GetEnumerator()/ MoveNext()/ Dispose()secuencia requerida por Any()comprobar para un no-vacío IEnumerable<T>secuencia .

Por tan sólo IEnumerable<T>, entonces Any()será generalmente más rápido, ya que sólo tiene que mirar a una iteración. Sin embargo, tenga en cuenta que la implementación de LINQ-to-Objects Count()comprueba ICollection<T>(utilizando .Countcomo una optimización), por lo que si su fuente de datos subyacente es directamente una lista / colección, no habrá una gran diferencia. No me preguntes por qué no usa el no genérico ICollection...

Por supuesto, si ha utilizado LINQ para filtrarlo, etc. ( Whereetc.), tendrá una secuencia basada en bloques iteradores, por lo que esta ICollection<T>optimización es inútil.

En general con IEnumerable<T>: seguir con Any();-p

Marc Gravell
fuente
99
Marc: ICollection <T> en realidad no deriva de ICollection. También me sorprendió, pero Reflector no miente.
Bryan Watts el
77
¿La implementación Any () no verifica la interfaz ICollection y luego la propiedad Count?
derigel
313
Creo que hay otra razón para usar Any () la mayor parte del tiempo. Señala la intención precisa del desarrollador. Si usted no está interesado en saber el número de elementos, pero sólo si hay algunos, a continuación, somecollection.Any () es más simple y más claro que somecollection.Count> 0
TJKjaer
13
@huttelihut - ¿Cuántos desarrolladores conoces que están realmente confundidos por la declaración (somecollection.Count > 0)? ¿Fue difícil de entender todo nuestro código antes de la introducción del método .Any () de LINQ?
CraigTP
25
@JLRishe: sigo sintiendo que someCollection.Count > 0es tan claro someCollection.Any()y tiene el beneficio adicional de un mayor rendimiento y de no requerir LINQ. Por supuesto, este es un caso muy simple y otras construcciones que usan operadores LINQ transmitirán la intención de los desarrolladores mucho más clara que la opción equivalente que no es LINQ.
CraigTP
65

Nota: escribí esta respuesta cuando Entity Framework 4 era real. El objetivo de esta respuesta era no entrar en pruebas triviales .Any()versus de .Count()rendimiento. El punto era señalar que EF está lejos de ser perfecto. Las versiones más nuevas son mejores ... pero si tiene una parte del código que es lenta y usa EF, pruebe con TSQL directo y compare el rendimiento en lugar de confiar en suposiciones (eso .Any()es SIEMPRE más rápido que .Count() > 0).


Si bien estoy de acuerdo con la mayoría de las respuestas y comentarios votados, especialmente sobre el punto, las Anyseñales del desarrollador son mejoresCount() > 0 , he tenido una situación en la que Count es más rápido por orden de magnitud en SQL Server (EntityFramework 4).

Aquí hay una consulta con Anyesa excepción de tiempo de espera (en ~ 200,000 registros):

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count versión ejecutada en cuestión de milisegundos:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Necesito encontrar una manera de ver qué SQL exacto producen ambos LINQ, pero es obvio que hay una gran diferencia de rendimiento entre County Anyen algunos casos, y desafortunadamente parece que no puede quedarse Anyen todos los casos.

EDITAR: Aquí se generan los SQL. Bellezas como puedes ver;)

ANY:

exec sp_executesql N'SELECT TOP (1) 
[Proyecto2]. [ContactId] AS [ContactId], 
[Proyecto2]. [CompanyId] AS [CompanyId], 
[Proyecto2]. [ContactName] AS [ContactName], 
[Proyecto2]. [Nombre completo] AS [Nombre completo], 
[Proyecto2]. [ContactStatusId] AS [ContactStatusId], 
[Proyecto2]. [Creado] AS [Creado]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Proyecto2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Creado] AS [Creado], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    DESDE (SELECCIONAR 
        [Extensión1]. [ContactId] AS [ContactId], 
        [Extensión1]. [CompanyId] AS [CompanyId], 
        [Extensión1]. [ContactName] AS [ContactName], 
        [Extensión1]. [Nombre completo] AS [Nombre completo], 
        [Extensión1]. [ContactStatusId] AS [ContactStatusId], 
        [Extensión1]. [Creado] AS [Creado]
        FROM [dbo]. [Contacto] AS [Extent1]
        DONDE ([Extent1]. [CompanyId] = @ p__linq__0) Y ([Extent1]. [ContactStatusId] <= 3) Y (NO EXISTE (SELECCIONE 
            1 AS [C1]
            DE [dbo]. [NewsletterLog] AS [Extent2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Proyecto2]
) AS [Proyecto2]
WHERE [Project2]. [Row_number]> 99
ORDER BY [Proyecto2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

COUNT:

exec sp_executesql N'SELECT TOP (1) 
[Proyecto2]. [ContactId] AS [ContactId], 
[Proyecto2]. [CompanyId] AS [CompanyId], 
[Proyecto2]. [ContactName] AS [ContactName], 
[Proyecto2]. [Nombre completo] AS [Nombre completo], 
[Proyecto2]. [ContactStatusId] AS [ContactStatusId], 
[Proyecto2]. [Creado] AS [Creado]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Proyecto2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Creado] AS [Creado], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    DESDE (SELECCIONAR 
        [Proyecto1]. [ContactId] AS [ContactId], 
        [Proyecto1]. [CompanyId] AS [CompanyId], 
        [Proyecto1]. [ContactName] AS [ContactName], 
        [Proyecto1]. [Nombre completo] AS [Nombre completo], 
        [Proyecto1]. [ContactStatusId] AS [ContactStatusId], 
        [Proyecto1]. [Creado] AS [Creado]
        DESDE (SELECCIONAR 
            [Extensión1]. [ContactId] AS [ContactId], 
            [Extensión1]. [CompanyId] AS [CompanyId], 
            [Extensión1]. [ContactName] AS [ContactName], 
            [Extensión1]. [Nombre completo] AS [Nombre completo], 
            [Extensión1]. [ContactStatusId] AS [ContactStatusId], 
            [Extensión1]. [Creado] AS [Creado], 
            (SELECCIONE 
                CONTAR (1) COMO [A1]
                DE [dbo]. [NewsletterLog] AS [Extent2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Contacto] AS [Extent1]
        ) AS [Proyecto1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) AND ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [Proyecto2]
) AS [Proyecto2]
WHERE [Project2]. [Row_number]> 99
ORDER BY [Proyecto2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Parece que puro Where with EXISTS funciona mucho peor que calcular Count y luego hacer Where with Count == 0.

Avíseme si ustedes ven algún error en mis hallazgos. Lo que se puede sacar de todo esto, independientemente de la discusión Any vs Count es que cualquier LINQ más complejo está mucho mejor cuando se reescribe como Procedimiento almacenado;).

nikib3ro
fuente
2
Me encantaría ver algunos planes de consultas SQL generados por cada consulta linq para cada escenario.
Pure.Krome
43
basado en el SQL, todo lo que puedo decir es: ambas consultas se ven horribles. Sabía que había una razón por la que normalmente escribo mi propio TSQL ...
Marc Gravell
! Cualquiera tendría que mirar a través de todas las filas como lo haría Count. ¡Que su ejemplo dé un resultado tan horrible es un poco extraño, en el peor de los casos! Cualquiera debería ser un poco más lento que Count. En su caso, buscaría formas de simplificar la selección, quizás dividiéndola en etapas o reordenando las condiciones si eso fuera posible. ¡Pero su argumento de que la regla Any es mejor que Count no se cumple! Any es mejor que Count es muy buena.
Doblado
25

Como este es un tema bastante popular y las respuestas difieren, tuve que volver a analizar el problema.

Prueba de env: EF 6.1.3, SQL Server, 300k registros

Modelo de mesa :

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

Código de prueba:

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

Resultados:

Cualquiera () ~ 3 ms

Count () ~ 230ms para la primera consulta, ~ 400ms para la segunda

Observaciones:

Para mi caso, EF no generó SQL como @Ben mencionado en su publicación.

kamil-mrzyglod
fuente
44
Para una comparación adecuada, debes hacer Count() > 0. : D
Andrew
1
Andrew, Count ()> 0 no se ejecutará de manera diferente que Count () en esta prueba en particular.
CodeMonkeyForHire
11

EDITAR: se corrigió en EF versión 6.1.1. y esta respuesta ya no es real

Para SQL Server y EF4-6, Count () se desempeña aproximadamente dos veces más rápido que Any ().

Cuando ejecutas Table.Any (), generará algo como ( alerta: no hagas daño al cerebro tratando de entenderlo )

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

eso requiere 2 escaneos de filas con su condición.

No me gusta escribir Count() > 0porque oculta mi intención. Prefiero usar un predicado personalizado para esto:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
Ben
fuente
Me di cuenta de esto también. El Any () SQL no tiene ningún sentido en absoluto. No estoy seguro de por qué no lo hacen: CASO CUANDO (EXISTE (sql)) LUEGO 1 OTRO 0 FIN. No puedo pensar en una razón por la que necesitan hacer un NO EXISTE para devolver 0.
scott.korin
Esto es falso Encontraste un mal plan de consulta por casualidad. Esto pasa. Cualquiera es, casi siempre, más rápido.
Usr
Verifiqué el sql generado en 6.1.3, lo arreglaron: SELECCIONE EL CASO CUANDO (EXISTE (SELECCIONE 1 COMO [C1] DESDE [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id]> 1000)) ENTONCES cast (1 como bit) ELSE cast (0 como bit) END AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1]
Ben
6

Depende, ¿qué tan grande es el conjunto de datos y cuáles son sus requisitos de rendimiento?

Si no es nada gigantesco, use la forma más legible, que para mí es cualquiera, porque es más corta y legible en lugar de una ecuación.

Timothy Gonzalez
fuente
2

Sobre el método Count () , si IEnumarable es una ICollection , entonces no podemos iterar en todos los elementos porque podemos recuperar el campo Count de ICollection , si IEnumerable no es una ICollection , debemos iterar en todos los elementos usando un tiempo con un MoveNext , echar un vistazo al código de .NET Framework:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

Referencia: Fuente de referencia Enumerable

Thiago Coelho
fuente
2

Puedes hacer una prueba simple para resolver esto:

var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

Verifique los valores de testCount y testAny.

Bronks
fuente
1
Aquí está la prueba con su código para la propiedad Count vs Any () La propiedad Count cuenta vs. Any () con + 2x - enlace
Stanislav Prusac
1
Para un mejor resultado, puede hacer estas comparaciones 1000 veces (o más). Ayuda a promediar los resultados y evitar picos aleatorios.
Romano
Cuando realice pruebas como el método mencionado anteriormente, debe considerar muchos más factores, como la carga en su base de datos / red, el almacenamiento en caché del plan en el lado de la base de datos, etc. Por lo tanto, para hacer una prueba precisa, también debe diseñar un entorno aislado y preciso
Vahid Farahmandian
para una mejor comparación se debe Countreemplazar por el método Count () vs .Any () no es una propiedad. Necesita tiempo de iteraciones.
daremachine
0

Si está utilizando Entity Framework y tiene una tabla enorme con muchos registros, Any () será mucho más rápido. Recuerdo una vez que quería comprobar si una mesa estaba vacía y tenía millones de filas. Tomó 20-30 segundos para que Count ()> 0 se completara. Fue instantáneo con Any () .

Any () puede ser una mejora del rendimiento porque es posible que no tenga que iterar la colección para obtener la cantidad de cosas. Solo tiene que golpear a uno de ellos. O, por ejemplo, para LINQ-to-Entities, el SQL generado será IF EXISTS (...) en lugar de SELECT COUNT ... o incluso SELECT * ....

Janmejay Kumar
fuente