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
«Такое чувство, что оно должно (то есть .Any () звучит так, как будто оно короткое замыкание, тогда как .All () звучит так, как будто это не так)» - Ни для кого не обладает звуковой интуицией. Логическая эквивалентность, которую вы отмечаете, подразумевает, что они одинаково замкнуты. Мгновенное размышление показывает, что Все могут выйти, как только возникнет не квалифицируемый случай.
Джим Балтер
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. Но нет, нет, нет. IL в значительной степени такой же, но для очевидной инверсии возврата true при совпадении предикатов и возврата false при несовпадении предикатов.

Это, конечно, только linq-для-объектов. Возможно, что какой-то другой поставщик linq рассматривает одного из них гораздо лучше, чем другого, но тогда, если бы это было так, случайным образом, какой из них получил более оптимальную реализацию.

Казалось бы, правило сводится исключительно к тому, кто чувствует себя if(determineSomethingTrue)проще и понятнее, чем if(!determineSomethingFalse). И, честно говоря, я думаю, что у них есть некоторая точка в том, что меня часто if(!someTest)смущает *, когда есть альтернативный критерий равной многословности и сложности, который вернул бы значение true для условия, в котором мы хотим действовать. Тем не менее, на самом деле, я лично не нахожу ничего, чтобы отдавать предпочтение одной из двух альтернатив, которые вы предоставляете, и, возможно, очень немного склонялся бы к первой, если бы предикат был более сложным.

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

Джон Ханна
источник
8
Я не уверен, что делается за линией, но для меня гораздо более читабельным является: если (не любой), чем если (все не равны).
VikciaR
49
Существует большая разница, когда ваше перечисление не имеет значений. «Any» всегда возвращает FALSE, а «All» всегда возвращает TRUE. Поэтому говорить, что один является логическим эквивалентом другого, не совсем верно!
Арно
44
@Arnaud Anyвернется falseи, следовательно !Any, вернется true, поэтому они идентичны.
Джон Ханна
11
@Arnaud Нет никого, кто прокомментировал бы, кто сказал, что Any и All являются логически эквивалентными. Или, другими словами, все, кто прокомментировал, не сказали, что Any and All являются логически эквивалентными. Эквивалентность находится между! Any (предикат) и All (! Предикат).
Джим Балтер
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
}
AakashM
источник
6
Спасибо - я уже думал о внедрении их в нашу библиотеку, но пока не решил, хорошая ли это идея. Я согласен, что они делают код более читабельным, но я обеспокоен тем, что они не добавляют достаточной ценности.
Марк
2
Я искал None и не нашел его. Это намного более читабельно.
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
@ Джон семантически ни один! = Все. Так что семантически у вас либо нет ни одного, ни всех, но в случае .All () ни один не является подмножеством всех коллекций, которые возвращают true для всех, и это несоответствие может привести к ошибкам, если вы об этом не знаете. +1 для этого Энтони
Руна FS
@RuneFS Я не подписан. С точки зрения семантики и логики «никто, где это неправда ...», действительно то же самое, что и «все, где это правда». Например, "где ни один из принятых проектов от нашей компании?" всегда будет иметь тот же ответ, что и «где все принятые проекты от других компаний?» ...
Джон Ханна
... Теперь верно, что у вас могут быть ошибки, если предположить, что "все элементы ..." означают, что есть по крайней мере один элемент, который является по крайней мере одним элементом, который выполняет тест, поскольку "все элементы ..." "всегда верно для пустого набора, я не оспариваю это вообще. Я добавил, что такая же проблема может возникнуть, если допустить, что «ни один из элементов ...» означает, что по крайней мере один элемент не соответствует тесту, поскольку «ни один из элементов ...» также всегда верен для пустого набора , Дело не в том, что я не согласен с мнением Энтони, а в том, что я думаю, что оно также справедливо для других двух обсуждаемых конструкций.
Джон Ханна
@ Джон, ты говоришь о логике, а я говорю о лингвистике. Человеческий мозг не может обработать негатив (до того, как он обработает позитив, и в этот момент он может затем отрицать его), поэтому в этом смысле между ними есть большая разница. Это не делает логику, которую вы предлагаете, неверной
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

По этой ссылке

Любой - Проверяет хотя бы одно совпадение

Все - Проверяет, что все совпадают

rcarvalhoxavier
источник
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

Если вы посмотрите на источник Enumerable, вы увидите, что реализация 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;
}

Невозможно, чтобы один метод был значительно быстрее, чем другой, поскольку единственное различие заключается в логическом отрицании, поэтому предпочтение читаемости ложному выигрышу в производительности.

Томас Аюб
источник