Який метод працює краще: .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.