LINQ .Any VS. Exists - Какая разница?

413

Используя LINQ для коллекций, в чем разница между следующими строками кода?

if(!coll.Any(i => i.Value))

а также

if(!coll.Exists(i => i.Value))

Обновление 1

Когда я разбираю, .Existsпохоже, что нет кода.

Обновление 2

Кто-нибудь знает, почему там нет кода для этого?

Энтони Д
источник
9
Как выглядит скомпилированный код? Как ты разбирал? ILDASM? Что вы ожидали найти, но не нашли?
Майнерсбур

Ответы:

423

Смотрите документацию

List.Exists (Метод объекта - MSDN)

Определяет, содержит ли List (T) элементы, которые соответствуют условиям, заданным указанным предикатом.

Это существует с .NET 2.0, так до LINQ. Предназначено для использования с делегатом Predicate , но лямбда-выражения обратно совместимы. Кроме того, только у Списка есть это (даже IList)

IEnumerable.Any (метод расширения - MSDN)

Определяет, удовлетворяет ли какой-либо элемент последовательности условию.

Это новое в .NET 3.5 и использует Func (TSource, bool) в качестве аргумента, так что это было предназначено для использования с лямбда-выражениями и LINQ.

По поведению они идентичны.

Meinersbur
источник
4
Позже я сделал сообщение в другой ветке, где я перечислил все «эквиваленты» Linq List<>методов экземпляра .NET 2 .
Джепп Стиг Нильсен
201

Разница в том, что Any - это метод расширения для любого, IEnumerable<T>определенного в System.Linq.Enumerable. Это может быть использовано в любом IEnumerable<T>случае.

Exists не является методом расширения. Я думаю, что колл имеет тип List<T>. Если так, то Exists - это метод экземпляра, который очень похож на Any.

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

  • Any также имеет перегрузку, которая не принимает параметров и просто ищет любой элемент в перечисляемом.
  • Exists не имеет такой перегрузки.
JaredPar
источник
13
Хорошо поставлено (+1). List <T> .Exists существует с .Net 2, но работает только для общих списков. IEnumerable <T> .Any был добавлен в .Net 3 как расширение, которое работает с любой перечисляемой коллекцией. Также есть похожие члены, такие как List <T> .Count, который является свойством, и IEnumerable <T> .Count () - метод.
Кит
51

TLDR; По производительности, Anyкажется, медленнее (если я настроил это правильно, чтобы оценить оба значения практически одновременно)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

генератор тестового списка:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

С 10М записями

«Любое: 00: 00: 00.3770377 Существует: 00: 00: 00.2490249»

С 5М записей

«Любое: 00: 00: 00.0940094 Существует: 00: 00: 00.1420142»

С 1М записей

«Любое: 00: 00: 00.0180018 Существует: 00: 00: 00.0090009»

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

«Существует: 00: 00: 00.0050005 Любой: 00: 00: 00.0100010»

С 100к записей

«Существует: 00: 00: 00.0010001 Любой: 00: 00: 00.0020002»

Казалось бы, Anyмедленнее по величине 2.

Редактировать: для записей 5 и 10M я изменил способ создания списка и Existsнеожиданно стал медленнее, чем то, Anyчто подразумевает, что что-то не так в моем тестировании.

Новый механизм тестирования:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

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

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10M

«Любое: 00: 00: 00.1640164 Существует: 00: 00: 00.0750075»

5M

«Любое: 00: 00: 00.0810081 Существует: 00: 00: 00.0360036»

1M

«Любое: 00: 00: 00.0190019 Существует: 00: 00: 00.0070007»

500k

«Любое: 00: 00: 00.0120012 Существует: 00: 00: 00.0040004»

введите описание изображения здесь

Матас Вайткявичюс
источник
3
Нет дискредитации для вас, но я чувствую скептическое отношение к этим критериям. Посмотрите на числа: у каждого результата есть рекурсия (3770377: 2490249). По крайней мере, для меня это верный признак того, что что-то не так. Я не уверен на сто процентов здесь по математике, но я думаю, что вероятность того, что повторяющийся паттерн произойдет, равна 1 на 999 ^ 999 (или 999! Может быть?) На значение. Так что вероятность того, что это произойдет 8 раз подряд, бесконечно мала. Я думаю, это потому, что вы используете DateTime для сравнительного анализа .
Джерри Кангасниеми
@JerriKangasniemi Повторение одной и той же операции в изоляции должно всегда занимать одинаковое количество времени, то же самое касается повторения ее несколько раз. Что заставляет вас говорить, что это DateTime?
Матас Вайткявичюс
Конечно, это так. Проблема по-прежнему заключается в том, что для вызовов 500 000, к примеру, вряд ли потребуется 0120012 секунд. И если бы он был совершенно линейным, объясняя, таким образом, числа так красиво, то вызовы 1M заняли бы 0240024 секунды (вдвое дольше), однако это не так. 1M звонков занимает 58, (3)% дольше, чем 500k, а 10M занимает 102,5% дольше, чем 5M. Так что это не линейная функция и, следовательно, не совсем разумно, чтобы числа повторялись. Я упомянул DateTime, потому что у меня были проблемы с ним в прошлом, потому что DateTime не использовал таймеры высокой точности.
Джерри Кангасниеми
2
@JerriKangasniemi Могу ли я предложить вам исправить это и опубликовать ответ
Матас Вайткявичюс
1
Если я правильно читаю ваши результаты, вы указали, что значение Any будет примерно в 2–3 раза больше, чем в Exists. Я не понимаю, как эти данные даже мягко подтверждают ваше утверждение, что «Казалось бы, что-нибудь медленнее по величине 2». Это немного медленнее, конечно, не на порядки.
Suncat2000
16

Как продолжение ответа Матаса на бенчмаркинг.

TL / DR : Exists () и Any () одинаково быстрые.

Прежде всего: бенчмаркинг с использованием секундомера не является точным ( см. Ответ series0ne на другую, но похожую тему ), но он гораздо точнее, чем DateTime.

Способ получить действительно точные показания с помощью профилирования производительности. Но один из способов получить представление о том, как производительность двух методов соотносится друг с другом, состоит в том, чтобы выполнить оба метода много раз, а затем сравнить самое быстрое время выполнения каждого из них. Таким образом, на самом деле не имеет значения, что JITing и другие шумы дают нам плохие показания (и это делает ), потому что оба исполнения « одинаково вводят в заблуждение » в некотором смысле.

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

После выполнения вышеуказанного кода 4 раза (что, в свою очередь, составляет 1 000 Exists()и Any()для списка с 1 000 000 элементов), нетрудно увидеть, что методы в значительной степени одинаково быстры.

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

Там является небольшая разница, но это слишком маленькая разница не может быть объяснена фоновым шумом. Я полагаю, что если сделать 10 000 или 100 000 Exists()и Any()вместо этого, эта небольшая разница исчезнет более или менее.

Джерри Кангасниеми
источник
Могу ли я предложить вам 10 000, 100 000 и 1000000, просто чтобы быть методичным по этому поводу, а также почему минимальное, а не среднее значение?
Матас Вайткявичюс
2
Минимальное значение - потому что я хочу сравнить самое быстрое выполнение (= наименьшее количество фонового шума) каждого метода. Я мог бы сделать это с большим количеством итераций, хотя это будет позже (я сомневаюсь, что мой босс хочет заплатить мне за это вместо того, чтобы работать через наше отставание)
Джерри Кангасниеми
Я спросил Пола Линдберга, и он сказал, что все в порядке;) Что касается минимума, я могу видеть ваши рассуждения, однако более ортодоксальный подход заключается в использовании среднего en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius
9
Если вы опубликовали код, который вы фактически выполнили, неудивительно, что вы получаете схожие результаты, так как вы называете Exists в обоих измерениях. ;)
Simon Touchtech
Хех, да, я тоже это видел, теперь ты это говоришь. Не в моем исполнении, хотя. Это было просто урезанным понятием того, что я сравнивал. : P
Джерри Кангасниеми
4

Кроме того, это будет работать, только если значение имеет тип bool. Обычно это используется с предикатами. Любой предикат, как правило, используется для определения того, существует ли какой-либо элемент, удовлетворяющий заданному условию. Здесь вы просто делаете карту из вашего элемента i в свойство bool. Он будет искать «я», свойство Value которого имеет значение true. После этого метод вернет true.

FLQ
источник
3

Когда вы исправите измерения - как упомянуто выше: Любое и Существует, и добавив среднее - мы получим следующий результат:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

Benchmark finished. Press any key.
jasmintmp
источник