Какой метод работает лучше:.Any () vs. Count ()> 0?

на System.Linq пространство имен, теперь мы можем расширить наши IEnumerable's, чтобы иметьAny() и Count() методы расширения.

недавно мне сказали, что если я хочу проверить, что коллекция содержит 1 или более элементов внутри нее, я должен использовать .Any() метод расширения вместо .Count() > 0 метод расширения, поскольку .Count() метод расширения должен перебирать все элементы.

во-вторых, некоторые коллекции имеют свойства (не метод расширения), что составляет Count или Length. Было бы лучше использовать их, а не .Any() или .Count() ?

да / нет ?

8 ответов


если вы начинаете с чего-то, что имеет .Length или .Count (например,ICollection<T>, IList<T>, List<T> и т. д.) - Тогда это будет самый быстрый вариант, так как ему не нужно проходить через GetEnumerator()/MoveNext()/Dispose() последовательность требуемых Any() чтобы проверить непустой IEnumerable<T> последовательности.

только IEnumerable<T>, потом Any() будет вообще быть быстрее, так как он должен смотреть только на одну итерацию. Однако обратите внимание, что реализация LINQ-to-Objects Count() кто ICollection<T> (через .Count как оптимизация) - поэтому, если ваш базовый источник данных напрямую список / коллекция, не будет большой разницы. Не спрашивайте меня, почему он не использует non-generic ICollection...

конечно, если вы использовали LINQ для фильтрации и т. д. (Where etc), у вас будет последовательность на основе итератора, и поэтому это ICollection<T> оптимизация-это бесполезно.

в целом IEnumerable<T> : палка с Any() - p


Примечание: Я написал этот ответ, когда Entity Framework 4 была актуальной. Смысл этого ответа был не в том, чтобы попасть в тривиальное .Any() vs .Count() тестирование производительности. Смысл был в том, чтобы сигнализировать, что EF далек от совершенства. Новые версии лучше... но если у вас есть часть кода, которая медленная, и она использует EF, протестируйте с помощью direct TSQL и сравните производительность, а не полагаясь на предположения (это .Any() всегда быстрее, чем .Count() > 0).


хотя я согласен с большинством голосов ответ и комментарии-особенно по пункту Any сигналы разработчик намерениях лучше, чем Count() > 0 - у меня была ситуация, в которой счетчик быстрее на порядок на SQL Server (EntityFramework 4).

вот запрос Any это исключение тайм-аута thew (на ~ 200.000 записях):

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 версия выполнена в считанные миллисекунды:

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();

мне нужно найти способ узнать, какой именно SQL оба LINQs производят - но очевидно, что существует огромная разница в производительности между Count и Any в некоторых случаях, и, к сожалению, кажется, вы не можете просто придерживаться Any во всех случаях.

EDIT: здесь генерируются SQLs. Красавицы, Как видите;)

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

кажется, что pure Where with EXISTS работает намного хуже, чем вычисление количества, а затем делает, где с Count == 0.

Дайте мне знать, если вы видите какие-то ошибки в моих выводах. Что можно извлечь из всего этого независимо от любого обсуждения vs Count, так это то, что любой более сложный LINQ лучше переписывается как хранимая процедура ;).


поскольку это довольно популярная тема и ответы отличаются, мне пришлось по-новому взглянуть на проблему.

тестирование env: EF 6.1.3, SQL Server, 300k records

таблица модель:

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

тестовый код:

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

результаты:

Any () ~ 3ms

Count () ~ 230ms для первого запроса, ~ 400ms для второго

Примечания:

для мой случай EF не генерировал SQL, как @Ben, упомянутый в его сообщении.


EDIT: Это было исправлено в EF версии 6.1.1. и этот ответ уже не актуален

для SQL Server и EF4-6 Count () работает примерно в два раза быстрее, чем Any ().

при запуске таблицы.Любой(), он будет генерировать что-то вроде(предупреждение: не повредите мозг, пытаясь понять его)

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]

для этого требуется 2 сканирования строк с вашим состоянием.

Я не люблю писать Count() > 0 потому что он скрывает мои намерение. Я предпочитаю использовать пользовательский предикат для этого:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

Ну .Count() метод расширения не используйте .Count свойство, но я бы предположил, что вы не будете использовать .Count() метод для простой коллекции, но скорее в конце оператора LINQ с критериями фильтрации и т. д.

в этой связи .Any() будет быстрее, чем .Count() > 0.


Это зависит от того, насколько большой набор данных и каковы ваши требования?

Если это ничего гигантского, используйте наиболее читаемую форму, который для меня любой, потому что он короче и читаем, а не уравнение.


вы можете сделать простой тест, чтобы выяснить это:

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;

Проверьте значения testCount и testAny.


о Count () способ, если IEnumarable - это ICollection, тогда мы не можем перебирать все элементы, потому что мы можем получить графа