Який метод працює краще: .Any () vs .Count ()> 0?


576

у System.Linqпросторі імен тепер ми можемо розширити наші IEnumerable, щоб використовувати методи розширення Any () та Count () .

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

По-друге, деякі колекції мають властивість (а не метод розширення), яка є Countабо Length. Чи було б краще використовувати ці, замість .Any()або .Count()?

так / ні?


Краще використовувати Any () на перелічених номерах та розраховувати на колекції. Якщо хтось відчуває, що запис "(somecollection.Count> 0)" буде плутати чи спричиняти проблеми з читальністю, краще напишіть це як метод розширення, назвіть його Any (). Тоді всі задоволені. Ефективність, а також читаність. Так що весь ваш код буде мати послідовність, і окремий розробник у вашому проекті не повинен турбуватися про вибір Count vs Any.
— Махеш

Відповіді:


708

Якщо ви починаєте з чим - то , що має .Lengthабо .Count(наприклад ICollection<T>, IList<T>, List<T>і т.д.) - то це буде найшвидшим варіантом, оскільки він не повинен пройти через GetEnumerator()/ MoveNext()/ Dispose()послідовність , необхідну Any()для перевірки непорожній IEnumerable<T>послідовності .

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

Звичайно, якщо ви використовували LINQ для його фільтрування тощо ( Whereтощо), у вас з'явиться послідовність на основі блоку ітераторів, і така ICollection<T>оптимізація марна.

Взагалі з IEnumerable<T>: палиця з Any();-p


9
Марк: ICollection <T> насправді не походить від ICollection. Я теж був здивований, але рефлектор не бреше.
— Брайан Уоттс

7
Чи не перевіряється будь-яка () реалізація для інтерфейсу ICollection та перевірка властивості Count?
— деригель

313
Я думаю, що є ще одна причина використання Any () більшості випадків. Це сигналізує про точний намір розробника. Якщо вам не цікаво знати кількість предметів, але лише якщо їх є, то деякіколекції. Кожен () простіший і зрозуміліший, ніж якийськолекція. Кількість> 0
— TJKjaer

13
@huttelihut - Скільки розробників ви знаєте, кого справді плутає заява (somecollection.Count > 0)? Чи був весь наш код до впровадження методу LINQ .Any () важко зрозуміти?
— CraigTP

25
@JLRishe - Я все ще вважаю, що someCollection.Count > 0це так само ясно someCollection.Any()і має додаткову перевагу більшої продуктивності та не вимагає LINQ. Зрозуміло, це дуже простий випадок, і інші конструкції, що використовують оператори LINQ, передадуть намірам розробників набагато чіткіше, ніж еквівалентний варіант, що не стосується LINQ.
— CraigTP

65

Примітка: цю відповідь я написав, коли Entity Framework 4 був фактичним. Сенс цієї відповіді полягав у тому, щоб не потрапляти на банальне .Any()порівняльне .Count()тестування. Справа полягала в тому, щоб сигналізувати, що EF далеко не ідеальний. Новіші версії кращі ... але якщо у вас є частина коду, який повільний, і він використовує EF, протестуйте з прямим TSQL і порівнюйте продуктивність, а не покладайтесь на припущення ( .Any()це ВЖЕ швидше, ніж .Count() > 0).


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

Ось запит із 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 виробляють обидва LINQ, але очевидно, що між ними 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]. [Створено] AS [Створено]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Створено] AS [Створено], row_number () НАД (ЗАМОВЛЕННЯ [Project2]. [ContactId] ASC) AS [row_number]
    ВІД (ВИБР.) 
        [Extent1]. [ContactId] AS [ContactId], 
        [Обсяг1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extent1]. [FullName] AS [FullName], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Обсяг1]. [Створено] ЯК [Створено]
        ВІД [dbo]. [Контакт] AS [Обсяг1]
        WHERE ([Extent1]. [CompanyId] = @ p__linq__0) AND ([Extent1]. [ContactStatusId] <= 3) І (НЕ існує) 
            1 AS [C1]
            ВІД [dbo]. [NewsletterLog] AS [Обсяг2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Project2]
) AS [Project2]
WHERE [Project2]. [Row_number]> 99
ЗАМОВИТИ [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]. [Створено] AS [Створено]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Створено] AS [Створено], row_number () НАД (ЗАМОВЛЕННЯ [Project2]. [ContactId] ASC) AS [row_number]
    ВІД (ВИБР.) 
        [Project1]. [ContactId] AS [ContactId], 
        [Project1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName], 
        [Project1]. [FullName] AS [FullName], 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Проект1]. [Створено] ЯК [Створено]
        ВІД (ВИБР.) 
            [Extent1]. [ContactId] AS [ContactId], 
            [Обсяг1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Обсяг1]. [Створено] ЯК [Створено], 
            (ВИБР 
                COUNT (1) AS [A1]
                ВІД [dbo]. [NewsletterLog] AS [Обсяг2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            ВІД [dbo]. [Контакт] AS [Обсяг1]
        ) AS [Проект1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) AND ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [Project2]
) AS [Project2]
WHERE [Project2]. [Row_number]> 99
ЗАМОВИТИ [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Це здається чистим. Де з EXISTS працює набагато гірше, ніж обчислити підрахунок, а потім зробити, де з Count == 0.

Повідомте мене, якщо ви, хлопці, бачите якусь помилку в моїх висновках. Що можна зробити з усього цього, незалежно від будь-якого обговорення проти підрахунку, - це те, що будь-який складніший LINQ є набагато кращим, коли він буде переписаний як Зберігана процедура;).


2
Я хотів би побачити деякі плани запитів Sql, які генеруються кожним linq-запитом для кожного сценарію.
— Pure.Krome

43
На основі SQL, я все можу сказати: обидва запити виглядають жахливо. Я знав, що є причина, що я зазвичай пишу свій власний TSQL ...
— Марк Гравелл

Будь-хто повинен був би переглядати всі рядки так само, як і граф. Те, що ваш приклад дає такий жахливий результат, трохи дивно, в гіршому випадку! Будь-який повинен бути лише трохи повільніше, ніж граф. У вашому випадку я б шукав способів спростити вибір, можливо, розділити його на етапи або переупорядкувати умови, якщо це можливо. Але Ваша думка, що правило "Будь-який краще, ніж Лічильник" не дотримується! Будь-який кращий, ніж "Граф" - дуже хороший.
— Вигнуто

25

Оскільки це досить популярна тема, і відповіді відрізняються, мені довелося по-новому поглянути на проблему.

Тестування env: EF 6.1.3, SQL Server, 300k записів

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

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

Результати:

Будь-який () ~ 3ms

Кількість () ~ 230 мс для першого запиту, ~ 400 мс для другого

Зауваження:

У моєму випадку EF не генерував SQL, як @Ben, згаданий у своєму дописі.


4
Для правильного порівняння вам слід зробити Count() > 0. : D
— Андрій

1
Ендрю, Count ()> 0 у цьому конкретному тесті не запускається інакше, ніж Count ().
— CodeMonkeyForHire

11

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

Я теж це помітив. SQL Any () взагалі не має сенсу. Я не впевнений, чому вони цього не роблять: СЛУЧАЙ КОЛИ (ІСНУЄТЬСЯ (sql)) ТОЖ 1 ЕЛЕЗ 0 КІН. Я не можу придумати причину, чому їм потрібно зробити НЕ ІСНУЮ, щоб повернути 0.
— scott.korin

Це помилково. Ви знайшли поганий план запитів випадково. Це трапляється. Будь-яке, майже завжди, швидше.
— usr

Я перевірив sql, згенерований в 6.1.3, виправив його: ВИБІР СЛУЧАК, КОЛИ (Є (Виберіть 1 як [С1] від [dbo]. ТОГО відступ (1 як біт) ELSE литий (0 як біт) КОНЕЦ ЯК [C1] ВІД (ВИБІР 1 AS X) AS [SingleRowTable1]
— Ben

6

Від цього залежить, наскільки великий набір даних і які ваші вимоги до продуктивності?

Якщо нічого гігантського, використовуйте найчитабельнішу форму, яка для мене є будь-якою, тому що вона коротша і читабена, а не рівняння.


2

Щодо методу Count () , якщо IEnumarable є ICollection , ми не можемо повторити всі елементи, оскільки ми можемо отримати поле Count під час ICollection , якщо IEnumerable не є ICollection, ми мусимо повторювати всі елементи, використовуючи деякий час з 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;
}

Довідка: Довідкове джерело Численні


2

Ви можете зробити простий тест, щоб зрозуміти це:

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.


1
Ось тест з вашим кодом на кількість власників проти будь-якого () Власність
— підрахунку

1
Для кращого результату ви можете зробити ці порівняння 1000 разів (або більше). Це допомагає середньо оцінити результати та уникнути випадкових стрибків.
— Роман

Під час тестування, як у вищезгаданому методі, вам потрібно врахувати ще багато факторів, такі як завантаження вашої бази даних / мережі, планування кешування на стороні бази даних тощо. Отже, щоб зробити точний тест, ви також повинні створити ізольоване та точне середовище
— Вахід Фарахмандян

для кращого порівняння слід Countзамінити метод Count () vs .Any (), а не властивість. Вам потрібен час ітерацій.
— смілиця

0

Якщо ви використовуєте Entity Framework і маєте величезну таблицю з багатьма записами, Any () буде набагато швидше. Я пам’ятаю, як одного разу мені хотілося перевірити, чи таблиця порожня і чи є мільйони рядків. Пройти 20-30 секунд, щоб кількість ()> 0 завершилася. Це було миттєво з Any () .

Будь-який () може бути підвищенням продуктивності, тому що, можливо, не доведеться повторювати колекцію, щоб отримати кількість речей. Просто треба вдарити по одному з них. Або, скажімо, для об'єктів LINQ, генерований SQL буде ЯКЩО ІСНУЄТЬСЯ (...), а не ВИБІР КОЛЕТУ ... або навіть ВИБІР * * ....

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.