.net - how - extended methods c#
¿Qué método funciona mejor:.Any() vs.Count()> 0? (8)
Acerca del método Count () , si IEnumarable es una ICollection , 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 , eche 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
en el espacio de nombres System.Linq
, ahora podemos extender nuestros IEnumerable
para que tengan los métodos de extensión Any()
y Count()
.
Me dijeron recientemente que si quiero comprobar que una colección contiene 1 o más elementos dentro de ella, debería usar el método de extensión .Any()
lugar del método de extensión .Count() > 0
porque el método de extensión .Count()
Tiene que iterar a través de todos los elementos.
En segundo lugar, algunas colecciones tienen una propiedad (no un método de extensión) que es Count
o Length
. ¿Sería mejor usar esos, en lugar de .Any()
o .Count()
?
si / nae
Bueno, el método de extensión .Count()
no usará la propiedad .Count
, pero supondría que no .Count()
método .Count()
para una colección simple, sino al final de una declaración LINQ con criterios de filtrado , etc.
En ese contexto, .Any()
será más rápido que .Count() > 0
.
Depende, ¿de qué tamaño 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.
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;
Compruebe los valores de testCount y testAny.
Si está comenzando con algo que tiene un .Length
o .Count
(como ICollection<T>
, IList<T>
, List<T>
, etc.), esta será la opción más rápida, ya que no es necesario IEnumerable<T>
secuencia GetEnumerator()
/ MoveNext()
/ Dispose()
requerida por Any()
para verificar si hay una secuencia IEnumerable<T>
no vacía.
Para IEnumerable<T>
, entonces Any()
generalmente será más rápido, ya que solo tiene que ver una iteración. Sin embargo, tenga en cuenta que la implementación LINQ-to-Objects de Count()
sí comprueba ICollection<T>
(usando .Count
como 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 ICollection
no genérico ...
Por supuesto, si ha usado LINQ para filtrarlo, etc. ( Where
etc.), tendrá una secuencia basada en bloques de iteradores, por lo que esta optimización de ICollection<T>
es inútil.
En general con IEnumerable<T>
: quédate con Any()
;-p
Ya que este es un tema bastante popular y las respuestas difieren, tuve que echar un vistazo al problema.
Pruebas 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:
Cualquier () ~ 3ms
Cuente () ~ 230ms para la primera consulta, ~ 400ms para la segunda
Observaciones:
Para mi caso, EF no generó SQL como @Ben mencionado en su publicación.
EDITAR: se ha corregido en EF versión 6.1.1. Y esta respuesta ya no es más real.
Para SQL Server y EF4-6, Count () se realiza 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]
que requiere 2 escaneos de filas con su condición.
No me gusta escribir Count() > 0
porque oculta mi intención. Prefiero usar 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;
}
}
Nota: escribí esta respuesta cuando Entity Framework 4 era real. El punto de esta respuesta no fue entrar en las pruebas de rendimiento triviales .Any()
vs .Count()
. 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 (que .Any()
SIEMPRE es más rápido que .Count() > 0
) .
Si bien estoy de acuerdo con la mayoría de las respuestas y comentarios más votados, especialmente sobre el punto del intento del desarrollador de Any
señal mejor que Count() > 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í está la consulta con Any
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 LINQs, pero es obvio que hay una gran diferencia de rendimiento entre Count
y Any
en algunos casos, y desafortunadamente parece que no puede quedarse con Any
en todos los casos.
EDITAR: Aquí se generan los SQL. Bellezas como puedes ver;)
ANY
exec sp_executesql N''SELECT TOP (1) [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created] FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number] FROM ( SELECT [Extent1].[ContactId] AS [ContactId], [Extent1].[CompanyId] AS [CompanyId], [Extent1].[ContactName] AS [ContactName], [Extent1].[FullName] AS [FullName], [Extent1].[ContactStatusId] AS [ContactStatusId], [Extent1].[Created] AS [Created] FROM [dbo].[Contact] AS [Extent1] WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( NOT EXISTS (SELECT 1 AS [C1] FROM [dbo].[NewsletterLog] AS [Extent2] WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId]) )) ) AS [Project2] ) AS [Project2] WHERE [Project2].[row_number] > 99 ORDER BY [Project2].[ContactId] ASC'',N''@p__linq__0 int'',@p__linq__0=4
COUNT
:
exec sp_executesql N''SELECT TOP (1) [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created] FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number] FROM ( SELECT [Project1].[ContactId] AS [ContactId], [Project1].[CompanyId] AS [CompanyId], [Project1].[ContactName] AS [ContactName], [Project1].[FullName] AS [FullName], [Project1].[ContactStatusId] AS [ContactStatusId], [Project1].[Created] AS [Created] FROM ( SELECT [Extent1].[ContactId] AS [ContactId], [Extent1].[CompanyId] AS [CompanyId], [Extent1].[ContactName] AS [ContactName], [Extent1].[FullName] AS [FullName], [Extent1].[ContactStatusId] AS [ContactStatusId], [Extent1].[Created] AS [Created], (SELECT COUNT(1) AS [A1] FROM [dbo].[NewsletterLog] AS [Extent2] WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) AS [C1] FROM [dbo].[Contact] AS [Extent1] ) AS [Project1] WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1]) ) AS [Project2] ) AS [Project2] WHERE [Project2].[row_number] > 99 ORDER BY [Project2].[ContactId] ASC'',N''@p__linq__0 int'',@p__linq__0=4
Parece que puro Where con EXISTS funciona mucho peor que calcular Count y luego hacer Where with Count == 0.
Déjame saber si ustedes ven algún error en mis hallazgos. Lo que se puede sacar de todo esto, independientemente de la discusión de Any vs Count, es que cualquier LINQ más complejo está mucho mejor cuando se reescribe como Procedimiento almacenado;).