Почему Enumerable.All возвращает true для пустой последовательности?

var strs = new Collection(); bool b = strs.All(str => str == "ABC"); 

Код создает пустой набор строк, затем пытается определить, являются ли все элементы в коллекции «ABC». Если вы запустите его, b будет правдой.

Но в коллекции нет никаких элементов, не говоря уже о любых элементах, которые равны «ABC».

Это ошибка, или есть разумное объяснение?

    Это, конечно, не ошибка. Он ведет себя точно так же, как задокументировано :

    true, если каждый элемент исходной последовательности проходит тест в указанном предикате, или если последовательность пуста ; в противном случае – false.

    Теперь вы можете спорить о том, должен ли он работать таким образом (мне кажется, это хорошо для меня, каждый элемент последовательности соответствует предикату), но самое первое, что нужно проверить, прежде чем спросить, является ли что-то ошибкой, является документация. (Это первое, что нужно проверить, как только метод ведет себя иначе, чем вы ожидали.)

    Все требует, чтобы предикат был истинным для всех элементов последовательности. Это явно указано в документации. Это тоже единственное, что имеет смысл, если вы думаете обо всем, как о логическом и между результатами предиката для каждого элемента. «Истина», которую вы выбрали для пустой последовательности, является элементом идентификации и операции. Точно так же ложь, которую вы получаете от Any для пустой последовательности, является идентификатором для логического или.

    Если вы думаете о «все» как о «нет элементов в последовательности, которые не являются», это может иметь больше смысла.

    Это true , поскольку ничто (условие не делает) делает его false .

    Документы, вероятно, объясняют это. (Джон Скит также упомянул что-то несколько лет назад)

    То же самое относится к Any (противоположность All ), возвращающему false для пустых множеств.

    Редактировать:

    Вы можете представить, что All будут реализованы семантически так же, как:

     foreach (var e in elems) { if (!cond(e)) return false; } return true; // no escape from loop 

    Метод циклически проходит через все элементы до тех пор, пока не найдет тот, который не удовлетворяет условию, или не обнаруживает, что это не сработало. Если никто не сработает, возвращается true.

    Итак, если нет элементов, возвращается true (поскольку не было ничего, что не удалось)

    Большинство ответов здесь, похоже, идут по строкам «потому что так определено». Но есть и логическая причина, почему это определяется таким образом.

    При определении функции вы хотите, чтобы ваша функция была как можно более общей, чтобы ее можно было применять к максимально возможному числу случаев. Скажем, например, что я хочу определить функцию Sum , которая возвращает сумму всех чисел в списке. Что он должен вернуть, когда список пуст? Если вы вернете произвольное число x , вы должны определить функцию как:

    1. Функция, которая возвращает сумму всех чисел в данном списке, или x если список пуст.

    Но если x равно нулю, вы также можете определить его как

    1. Функция, которая возвращает x плюс заданные числа.

    Заметим, что определение 2 подразумевает определение 1, но 1 не означает 2, когда x не равно нулю, что само по себе является достаточным основанием для выбора 2 над 1. Но также примечание 2 является более изящным и, по своей сути, более общим, чем 1 . Походит на то, чтобы прожектор находился дальше, чтобы он осветил большую площадь. На самом деле намного больше. Я сам не математик, но я уверен, что они найдут тонну связей между определением 2 и другими математическими концепциями, но не так много связаны с определением 1, когда x не равен нулю.

    В общем, вы можете и, скорее всего, захотите вернуть элемент идентификации (тот, который оставит другой операнд без изменений) всякий раз, когда у вас есть функция, которая применяет двоичный оператор по набору элементов, а набор пуст. Это по той же причине, что функция Product вернет 1, когда список пуст (обратите внимание, что вы можете просто заменить « x plus» на «один раз» в определении 2). И по той же причине All (что можно рассматривать как повторное применение логического оператора И) вернет true когда список пуст ( p && true эквивалентен p ) и по той же причине Any (оператор OR) вернет false .

    Сохранение реализации. Действительно ли это имеет значение, если это правда? Посмотрите, есть ли у вас код, который выполняет итерацию по перечислимому и выполняет некоторый код. если All () истинно, тогда этот код все равно не будет запускаться, поскольку перечисляемый не имеет в нем никаких элементов.

     var hungryDogs = Enumerable.Empty(); bool allAreHungry = hungryDogs.All(d=>d.Hungry); if (allAreHungry) foreach (Dog dog in hungryDogs) dog.Feed(biscuits); <--- this line will not run anyway. 

    Вот расширение, которое может делать то, что OP хочет сделать:

     static bool All(this IEnumerable source, Func predicate, bool mustExist) { foreach (var e in source) { if (!predicate(e)) return false; mustExist = false; } return !mustExist; } 

    … и, как уже отмечали другие, это не ошибка, а хорошо документированное намеренное поведение.

    Альтернативное решение, если вы не хотите писать новое расширение:

     strs.DefaultIfEmpty().All(str => str == "ABC"); 

    PS: Вышеупомянутое не работает, если вы ищете значение по умолчанию! (Который для строк был бы нулевым.) В таких случаях он становится менее изящным с чем-то похожим на:

     strs.DefaultIfEmpty(string.Empty).All(str => str == null); 

    (Я рекомендую общий подход к расширению, но особенно для таких случаев.)

    Давайте будем гением компьютера.