LINQ: Не будь-який проти всіх


272

Часто я хочу перевірити, чи відповідає вказане значення одному зі списку (наприклад, під час перевірки):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Нещодавно я помітив, що ReSharper просить мене спростити ці запити до:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Очевидно, що це логічно ідентично, можливо, трохи читабельніше (якщо ви багато працювали з математики), моє запитання: чи це призводить до результативності?

Це здається, що слід (тобто .Any()звучить так, як це коротке замикання, тоді як .All()звучить так, як ні), але мені немає чого це обґрунтувати. Хтось має більш глибокі знання щодо того, чи вирішуватимуть запити те саме, чи ReSharper мене обманює?


6
Ви спробували розібрати код Linq, щоб побачити, що він робить?
RQDQ

9
У цьому випадку я б насправді пішов з if (! AcceptValues.Contains (someValue)), але, звичайно, це не питання :)
csgero

2
@csgero Я згоден Сказане було спрощенням (можливо, надмірним спрощенням) реальної логіки.
Марк

1
"Це відчуває себе так, як слід (т. К. Кожен () звучить так, як це коротке замикання, тоді як. Всі () звучать так, як ні)" - не для когось із здоровими інтуїціями. Логічна еквівалентність, яку ви зазначаєте, означає, що вони однаково короткі. На мить з’являється думка, що всі можуть припинити роботу, як тільки зустрінеться невідповідна справа.
Джим Балтер

3
Я не погоджуюсь з цим щодо ReSharper. Пишіть розумні потяги думки. Якщо ви хочете кинути виняток , якщо потрібно елемент відсутній: if (!sequence.Any(v => v == true)). Якщо ви хочете продовжити , тільки якщо все , що відповідає до певної специфікації: if (sequence.All(v => v < 10)).
Тимо

Відповіді:


344

Впровадження Allвідповідно до ILSpy (як я насправді пішов і подивився, а не "ну, цей метод працює дещо як ..." Я можу зробити, якби ми обговорювали теорію, а не вплив).

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

Впровадження Anyвідповідно до ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

Звичайно, може бути деяка тонка різниця в отриманому ІЛ. Але ні, ні там немає. IL майже однаковий, але для очевидної інверсії повернення істини на предикатну відповідність проти повернення false в невідповідності присудка.

Це звичайно лише linq-for-objects. Цілком можливо, що якийсь інший постачальник linq ставиться до одного набагато краще, ніж до іншого, але тоді, якщо це було так, то це майже випадково, який з них отримав більш оптимальну реалізацію.

Здавалося б, правило зводиться виключно до того, хто відчуває себе if(determineSomethingTrue)простіше і читабельніше, ніж if(!determineSomethingFalse). І чесно кажучи, я думаю, що вони мають певний сенс у тому, що я часто if(!someTest)буваю заплутаним *, коли існує альтернативний тест на рівність багатослів’я та складності, який би повернув істину для того стану, на який ми хочемо діяти. І справді, я особисто не знаходжу нічого, що надає перевагу одній з двох альтернатив, які ви даєте, і, можливо, дуже трохи схиляється до першої, якби предикат був складнішим.

* Не заплутаний, як в «Я не розумію», але заплутаний, як і я переживаю, що є якась тонка причина для рішення, яке я не розумію, і потрібно кілька розумових пропусків, щоб зрозуміти, що «ні, вони просто вирішили зробити так, чекайте, що я знову дивився на цей шматочок коду? ... "


8
Я не впевнений, що робиться за лініями, але для мене набагато читабельнішим є: якщо (не будь-який), ніж якщо (усі не рівні).
VikciaR

49
Існує велика різниця, коли ваше перерахування не має значень. "Будь-який" завжди повертає ЛІЖНЕ, а "Усі" - завжди ПРАВИЛЬНО. Так що сказати, що один є логічним еквівалентом іншого, не зовсім правда!
Арно

44
@Arnaud Anyповернеться falseі, отже !Any, повернеться true, тож вони однакові.
Джон Ханна

11
@Arnaud Немає жодної людини, яка б прокоментувала, яка б сказала, що будь-яке та всі логічно рівнозначні. Або кажучи інакше, усі, хто коментував, не казали, що будь-який та всі логічно рівнозначні. Еквівалентність між! Будь-яким (присудок) і всім (! Присудок).
Джим Балтер

7
@MacsDickinson це зовсім не різниця, тому що ви не порівнюєте протилежні предикати. Еквівалент !test.Any(x => x.Key == 3 && x.Value == 1)цього використання Allє test.All(x => !(x.Key == 3 && x.Value == 1))(що дійсно еквівалентно test.All(x => x.Key != 3 || x.Value != 1)).
Джон Ханна

55

Можливо, ви знайдете, що ці методи розширення полегшують ваш код:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

Тепер замість вашого оригіналу

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

можна сказати

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}

6
Дякую - я вже думав реалізувати це у нашій спільній бібліотеці, але ще не вирішив, чи це ще гарна ідея. Я погоджуюся, що вони роблять код більш читабельним, але я переживаю, що вони не додають достатнього значення.
Марк

2
Я шукав жодного і не знайшов його. Це набагато читабельніше.
Rhyous

Мені довелося додати нульові перевірки: return source == null || ! source.Any (присудок);
Rhyous

27

Обидва мають однакову ефективність, тому що обидва зупинення перерахування після результату можна визначити - Any()на першому елементі пройдений предикат оцінюється до trueта All()на першому елементі, який оцінює предикат false.


21

All коротке замикання на перший невідповідний, тому це не проблема.

Одна з тонкощів - це те

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

Правда. Усі елементи в послідовності є парними.

Докладніше про цей метод див. У документації для Enumerable.All .


12
Так, але bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)це правда теж.
Джон Ханна

1
@Jon семантично немає! = Все. Тож семантично у вас немає або всіх, але у випадку .All () жодна не є лише підмножиною всіх колекцій, які повертають true для всіх, і це невідповідність може призвести до помилок, якщо ви про це не знаєте. +1 для цього Ентоні
Руна ФС

@RuneFS Я не дотримуюся. Семантично і логічно "ніхто там, де це неправда, що ..." насправді те саме, що "все там, де це правда". Наприклад, "де жоден із прийнятих проектів від нашої компанії?" завжди матиме таку ж відповідь, як "де всі прийняті проекти від інших компаній?" ...
Джон Ханна

... Тепер це правда, що у вас можуть виникнути помилки, якщо припустити, що "всі елементи є ..." означає, що є принаймні один предмет, який є принаймні одним елементом, який відповідає тесту, оскільки "всі предмети ... "завжди справедливо для порожнього набору, я не заперечую це взагалі. Хоча я додав, що така ж проблема може трапитися і з припущенням, що "жоден з предметів ..." означає, що принаймні один предмет не відповідає тесту, оскільки "жоден з елементів ..." також завжди справедливий для порожнього набору . Це не те, що я не згоден з точкою Ентоні, це те, що я вважаю, що це стосується і інших обговорених двох конструкцій.
Джон Ханна

@Jon, ти розмовляєш з логікою, а я - з лінгвістикою. Людський мозок не може обробити негатив (перед тим, як обробити позитив, в який момент він може потім заперечувати його), тому в цьому сенсі між ними є велика різниця. Це не робить логіку, яку ви пропонуєте неправильною
Rune FS

8

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

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true

7

За цим посиланням

Будь-який - перевіряє принаймні одну відповідність

Усі - перевіряє, що всі відповідають


1
ви маєте рацію, але вони одночасно зупиняються на даній колекції. Усі перерви, коли стан не вдається, і будь-які перерви, коли вони відповідають вашому предикату. Тож технічно нічим не відрізняється, окрім сценічно
WPFKK

6

Як добре відповіли інші відповіді: мова йде не про ефективність, а про ясність.

Існує широка підтримка обох варіантів:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Але я думаю, що це може досягти широкої підтримки :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

Просто обчислення булевого (і іменування його) перед тим, як заперечувати що-небудь, це багато що впадає в голову.


3

Якщо ви подивитесь на безліч джерел, ви побачите, що реалізація Anyта Allє досить близькою:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

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

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