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

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

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

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

yea/nae?

Ответы

Ответ 1

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

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

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

В общем случае с IEnumerable<T>: придерживаться Any(); -p

Ответ 2

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


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

Вот запрос с Any который имеет исключение тайм-аута (на ~ 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: Здесь создаются SQL. Красавицы, как вы можете видеть;)

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, а затем делает Where with Count == 0.

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

Ответ 3

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

Тестирование env: EF 6.1.3, SQL Server, записи 300 тыс.

Модель таблицы:

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 для первого запроса, ~ 400 мс для второго

Примечания:

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

Ответ 4

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

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

Когда вы запустите Table.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;
    }
}

Ответ 5

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

В этом контексте .Any() будет быстрее, чем .Count() > 0.

Ответ 6

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

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

Ответ 7

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

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.

Ответ 8

О методе Count(), если IEnumarable является ICollection, мы не можем перебирать все элементы, потому что мы можем получить поле Count ICollection, если IEnumerable не является ICollection, мы должны перебирать все элементы, используя while MoveNext, посмотрите код.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;
}

Ссылка: исходный источник перечислимый