LINQ. Кожен VS .Exists - в чому різниця?


413

Використовуючи LINQ у колекціях, яка різниця між наступними рядками коду?

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

і

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

Оновлення 1

Коли я розбираю, .Existsсхоже, що коду немає.

Оновлення 2

Хтось знає, чому для цього немає коду?


9
Як виглядає створений вами код? Як ви розібрали? ілдазм? Що ви очікували знайти, але не зробили?
Meinersbur

Відповіді:


423

Дивіться документацію

List.Exists (метод об'єкта - MSDN)

Визначає, чи містить список (T) елементи, що відповідають умовам, визначеним вказаним предикатом.

Це існує з .NET 2.0, тому перед LINQ. Мається на увазі, що використовується з делегатом предиката , але лямбда-вирази сумісні з зворотним ходом. Також у списку це є (навіть не IList)

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

Визначає, чи задовольняє будь-який елемент послідовності умові.

Це нове в .NET 3.5 і використовує Func (TSource, bool) в якості аргументу, тому це було призначено для використання з лямбда-виразами та LINQ.

У поведінці вони ідентичні.


4
Пізніше я зробив публікацію в іншій темі, де я перерахував усі "еквіваленти" Linq List<>методів .NET 2 .
Jeppe Stig Nielsen

201

Різниця полягає в тому, що Any - це метод розширення для будь-якого, IEnumerable<T>визначеного на System.Linq.Enumerable. Його можна використовувати в будь-якому IEnumerable<T>випадку.

Існує не представляється методом розширення. Я здогадуюсь, що coll має тип List<T>. Якщо так, то існує існує екземплярний метод, який дуже схожий на будь-який.

Коротше кажучи , методи по суті однакові. Одне більш загальне, ніж інше.

  • Будь-яка також має перевантаження, яка не приймає жодних параметрів і просто шукає будь-який елемент із перелічених номерів.
  • У існуючих немає такого перевантаження.

13
Добре кажучи (+1). Список <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 та 10 мільйон я змінив спосіб генерування списку і 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"))
            {
            }
        }
    }

10М

"Будь-який: 00: 00: 00.1640164 Існує: 00: 00: 00.0750075"

"Будь-який: 00: 00: 00.0810081 Існує: 00: 00: 00.0360036"

"Будь-який: 00: 00: 00.0190019 Існує: 00: 00: 00.0070007"

500к

"Будь-який: 00: 00: 00.0120012 Існує: 00: 00: 00.0040004"

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


3
Ніякої дискредитації до вас, але я скептично ставлюсь до цих орієнтирів. Подивіться на цифри: кожен результат має рекурсію (3770377: 2490249). Принаймні для мене це впевнений знак, що щось невірно. Я не на сто відсотків впевнений у математиці тут, але я думаю, що шанси на цю повторювану модель є 1 на 999 ^ 999 (або 999! Можливо?) На значення. Тож шанс того, що це станеться 8 разів поспіль, нескінченно малий. Я думаю, це тому, що ви використовуєте DateTime для порівняльного аналізу .
Джеррі Кангаснємі

@JerriKangasniemi Повторення однієї і тієї ж операції ізольовано завжди має займати однакову кількість часу, те саме стосується повторення її кілька разів. Що змушує вас сказати, що це DateTime?
Матас Вайткевічус

Звичайно, так і є. Проблема все ще полягає в тому, що навряд чи потрібно взяти, наприклад, 0120012 секунд для 500k дзвінків. І якби це було ідеально лінійно, таким чином так чітко пояснюючи номери, 1М-дзвінки зайняли б 0240024 секунди (вдвічі довше), однак це не так. 1М дзвінків займає 58, (3)% довше 500к, а 10М - 102,5% довше, ніж 5М. Таким чином, це не лінійна функція і, отже, не дуже розумна, щоб числа повторювалися. Я згадав DateTime, тому що в мене раніше виникали проблеми з ним, оскільки DateTime не використовував таймери високої точності.
Jerri Kangasniemi

2
@JerriKangasniemi Чи можу я запропонувати вам виправити це та залишити відповідь
Matas Vaitkevicius

1
Якщо я правильно читаю ваші результати, ви повідомили, що "Будь-який" приблизно в 2 - 3 рази перевищує швидкість існування. Я не бачу, як ці дані навіть м'яко підтверджують ваше твердження, що "Здавалося б, будь-яке повільніше на величину 2". Це трохи повільніше, звичайно, не на порядки.
Suncat2000

16

Як продовження відповіді Матаса про тестування.

TL / DR : Існує () і будь-який () однаково швидко.

По-перше: Бенчмаркінг із використанням секундоміра не є точним ( див. Відповідь серії0ne на іншу, але подібну тему ), але набагато точніший, ніж DateTime.

Шлях отримати дійсно точні показання - це використання Performance Profiling. Але один із способів отримати розуміння того, як ефективність двох методів вимірюють один одного, - виконати обидва способи навантаження разів, а потім порівняти найшвидший час виконання кожного з них. Таким чином, це дійсно не має значення , що 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_ectivity#Practice
Matas Vaitkevicius

9
Якщо ви опублікували код, який ви насправді виконали, не дивно, що ви отримаєте подібні результати, як ви називаєте "Існуючі" в обох вимірах. ;)
Simon Touchtech

Хе, так, я також це бачив. Хоча не в моєму виконанні. Це було покликане позбавити концепцію того, що я порівнював. : P
Джеррі Кангасніемі

4

Крім того, це буде працювати лише в тому випадку, якщо значення має тип bool. Зазвичай це використовується з предикатами. Будь-який предикат, як правило, використовується, з'ясуйте, чи є елемент, що задовольняє заданій умові. Тут ви просто робите карту зі свого елемента i до властивості bool. Він буде шукати "i", властивість якого значення "true". Після закінчення метод поверне справжню.


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