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

578

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

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

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

да / нет?

Pure.Krome
источник
Лучше использовать Any () на Enumerables и рассчитывать на коллекции. Если кто-то чувствует, что написание '(somecollection.Count> 0)' приведет к путанице или проблемам с читабельностью, лучше напишите его как метод расширения с именем Any (). Тогда все довольны. Производительность, а также удобочитаемость. Так что весь ваш код будет иметь согласованность и отдельный разработчик в вашем проекте не должен беспокоиться о выборе Count vs Any.
Махеш
Вы видели Count ()> 0 против Any (), но видели ли вы Distinct (). Count ()> 1 против Distinct (). Skip (1) .Any ()? Это последнее, безусловно, намного быстрее для большого количества элементов, где Count фактически должен выполнять итерацию по всему набору, чтобы получить счет. Skip (1) .Any () избегает полного перечисления. 100 тыс. Итераций проверки для массива строк из 1000 элементов с 1-символьными строками, который выполняется за 4000 мс для Count ()> 1, для Skip (1) .Any () выполняется всего за 20 мс.
Триынко

Ответы:

710

Если вы начинаете с чем - то , что имеет .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

Марк Гравелл
источник
9
Марк: ICollection <T> на самом деле не происходит от ICollection. Я тоже был удивлен, но Reflector не лжет.
Брайан Уоттс
7
Разве реализация Any () не проверяет интерфейс ICollection и не проверяет свойство Count?
derigel
313
Я думаю, что есть другая причина для использования Any () большую часть времени. Это сигнализирует о точном намерении разработчика. Если вам не интересно знать количество элементов, но только если их есть, то somecollection.Any () проще и понятнее, чем somecollection.Count> 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 сигналов лучше, чем у меня, я столкнулся с ситуацией, когда на 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во всех случаях.

РЕДАКТИРОВАТЬ: Здесь генерируются 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]
ОТ (ВЫБЕРИТЕ [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Создано] AS [Создано], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    ОТ (ВЫБРАТЬ 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extent1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extent1]. [FullName] AS [FullName], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extent1]. [Создано] AS [Создано]
        FROM [dbo]. [Contact] AS [Extent1]
        ГДЕ ([Extent1]. [CompanyId] = @ p__linq__0) И ([Extent1]. [ContactStatusId] <= 3) И (НЕ СУЩЕСТВУЕТ (ВЫБРАТЬ) 
            1 AS [C1]
            FROM [dbo]. [NewsletterLog] AS [Extent2]
            ГДЕ ([Extent1]. [ContactId] = [Extent2]. [ContactId]) И (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Project2]
) AS [Project2]
ГДЕ [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]. [Created] AS [Created]
ОТ (ВЫБЕРИТЕ [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Создано] AS [Создано], row_number () OVER (ORDER BY [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], 
        [Project1]. [Создано] AS [Создано]
        ОТ (ВЫБРАТЬ 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [Created] AS [Created], 
            (ВЫБРАТЬ 
                COUNT (1) AS [A1]
                FROM [dbo]. [NewsletterLog] AS [Extent2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) И (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Contact] AS [Extent1]
        ) AS [Project1]
        ГДЕ ([Project1]. [CompanyId] = @ p__linq__0) И ([Project1]. [ContactStatusId] <= 3) И (0 = [Проект1]. [C1])
    ) AS [Project2]
) AS [Project2]
ГДЕ [Project2]. [Row_number]> 99
ЗАКАЗАТЬ [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

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

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

nikib3ro
источник
2
Хотелось бы увидеть некоторые планы Sql Query, которые генерируются каждым linq-запросом для каждого сценария.
Pure.Krome
43
на основании SQL все, что я могу сказать: оба запроса выглядят ужасно. Я знал, что есть причина, по которой я обычно пишу свой собственный TSQL ...
Марк Грэвелл
Любой должен был бы просмотреть все строки так же, как и граф. То, что ваш пример дает такой ужасный результат, в худшем случае немного странно! Любое должно быть только немного медленнее, чем Count. В вашем случае я бы искал способы упростить выбор, возможно, разбить его на этапы или изменить порядок условий, если это возможно. Но ваша точка зрения о том, что правило Any лучше, чем Count, не имеет места! Any лучше, чем Count, очень хороша.
согнут
25

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

Тестирование 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();
        }
    }
}

Результаты:

Любой () ~ 3 мс

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

Примечания:

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

камил-mrzyglod
источник
4
Для правильного сравнения, вы должны сделать Count() > 0. : D
Андрей
1
Эндрю, Count ()> 0 не будет работать иначе, чем Count () в этом конкретном тесте.
CodeMonkeyForHire
11

РЕДАКТИРОВАТЬ: это было исправлено в версии 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;
    }
}
Бен
источник
Я тоже это заметил. Any () SQL не имеет никакого смысла вообще. Я не уверен, почему они не делают: СЛУЧАЙ, КОГДА (СУЩЕСТВУЕТ (sql)), ТОГДА 1 ИЛИ 0, КОНЕЦ Я не могу придумать причину, почему они должны сделать НЕ СУЩЕСТВУЮ, чтобы вернуть 0.
scott.korin
Это неверно Вы нашли плохой план запроса случайно. Это случилось. Любой, почти всегда, быстрее.
usr
Я проверил sql, сгенерированный в 6.1.3, они исправили его: SELECT CASE WHEN (EXISTS (SELECT 1 AS [C1] FROM [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id]> 1000)) ТОГДА приведение (1 как бит) ИЛИ приведение (0 как бит) КОНЕЦ КАК [С1] ОТ (ВЫБРАТЬ 1 КАК X) КАК [SingleRowTable1]
Бен
6

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

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

Тимоти Гонсалес
источник
2

Что касается метода Count () , если IEnumarable является ICollection , то мы не можем выполнить итерацию по всем элементам, потому что мы можем извлечь поле Count из ICollection , если IEnumerable не является ICollection, мы должны выполнить итерацию по всем элементам, используя некоторое время с a MoveNext , посмотрите .NET Framework Code:

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

Ссылка: Ссылочный источник Enumerable

Тьяго Коэльо
источник
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.

Bronks
источник
1
Вот тест с вашим кодом для свойства Count в сравнении с Any () Свойство Count выигрывает в сравнении с Any () с + 2x - ссылкой
Станислав Прусак
1
Для лучшего результата вы можете сделать это сравнение 1000 раз (или больше). Это помогает усреднить результаты и избежать случайных всплесков.
Роман
Когда вы проводите тестирование, как и вышеупомянутый метод, вам необходимо учитывать множество других факторов, таких как нагрузка на вашу базу данных / сеть, планирование кэширования на стороне базы данных и т. Д. Поэтому для проведения точного теста вы должны также разработать изолированную и точную среду.
Вахид Фарамандян
для лучшего сравнения следует Countзаменить методом Count () vs .Any (), а не свойством. Вам нужно время итераций.
Daremachine
0

Если вы используете Entity Framework и у вас огромная таблица с множеством записей, Any () будет намного быстрее. Я помню, как однажды я хотел проверить, была ли таблица пустой и в ней миллионы строк. Для завершения Count ()> 0 потребовалось 20-30 секунд. Это было мгновенно с Any () .

Any () может повысить производительность, потому что может не потребоваться повторять коллекцию, чтобы получить количество вещей. Это просто должно ударить одного из них. Или, скажем, для LINQ-to-Entities, сгенерированный SQL будет IF EXISTS (...), а не SELECT COUNT ... или даже SELECT * ....

Янмехай Кумар
источник