Подтвердить что ты не робот

Почему IQueryable.All() возвращает true в пустой коллекции?

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

Метод All() позволяет вам предоставить предикат (например, выражение лямбда) для тестирования IQueryable, возвращая логическое значение, указывающее, соответствуют ли все члены коллекции тесту. Все идет нормально. Здесь, где это становится странным. All() также возвращает true, если коллекция пуста. Это кажется мне полностью обратным, по следующим причинам:

  • Если коллекция пуста, такой тест, в лучшем случае, undefined. Если моя подъездная дорога пуста, я не могу утверждать, что все автомобили, припаркованные там, красные. При таком поведении на пустой дороге все припаркованные автомобили есть красная И синяя И шахматная доска - все эти выражения вернутся к истине.
  • Для тех, кто знаком с понятием SQL, что NULL!= NULL, это неожиданное поведение.
  • Метод Any() работает так, как ожидалось, и (правильно) возвращает false, потому что у него нет элементов, соответствующих предикату.

Итак, мой вопрос: почему All() ведет себя так? Какую проблему он решает? Это нарушает принцип наименьшего удивления?

Я отметил этот вопрос как .NET 3.5, хотя поведение также относится и к .NET 4.0.

РЕДАКТИРОВАТЬ Хорошо, поэтому я понимаю логический аспект этого, столь превосходно изложенный Джейсоном и остальными вами. По общему признанию, пустая коллекция является чем-то вроде краевого случая. Я предполагаю, что мой вопрос связан с борьбой, потому что что-то логично, это не значит, что это обязательно имеет смысл, если вы не в правильном настроении.

4b9b3361

Ответ 1

Если моя подъездная дорога пуста, я не могу утверждать, что все автомобили, припаркованные там, красные.

Рассмотрим следующие утверждения.

S1: Моя подъездная дорога пуста.

S2: Все автомобили, припаркованные на моей подъездной дорожке, красные.

Я утверждаю, что S1 означает S2. То есть утверждение S1 => S2 истинно. Я сделаю это, показывая, что его отрицание ложно. В этом случае отрицание S1 => S2 составляет S1 ^ ~S2; это потому, что S1 => S2 является ложным только тогда, когда S1 истинно, а S2 - false. Каково отрицание S2?

~S2: На моей подъездной дорожке есть машина, которая не красная.

Какова истинностная ценность S1 ^ ~S2? Пусть написано это

S1 ^ ~S2: Моя подъездная дорога пуста, и на моей дороге есть машина, которая не красная.

Единственный способ, которым S1 ^ ~S2 может быть истинным, - это то, что оба S1 и ~S2 являются истинными. Но S1 говорит, что моя подъездная дорога пуста, а S2 говорит, что на моей дороге есть автомобиль. Моя подъездная дорога не может быть пустой и содержать автомобиль. Таким образом, для S1 и ~S2 невозможно быть истинным. Следовательно, S1 ^ ~S2 неверно, поэтому его отрицание S1 => S2 истинно.

Поэтому, если ваша дорога пуста, вы можете утверждать, что все автомобили, припаркованные там, красные.

Итак, рассмотрим a IEnumerable<T> elements и a Predicate<T> p. Предположим, что elements пусто. Мы хотим узнать значение

bool b = elements.All(x => p(x));

Рассмотрим его отрицание

bool notb = elements.Any(x => !p(x));

Для notb, чтобы быть истинным, должно быть хотя бы одно x в elements, для которого !p(x) истинно. Но elements пусто, поэтому невозможно найти x, для которого !p(x) истинно. Поэтому notb не может быть истинным, поэтому он должен быть ложным. Поскольку notb является ложным, его отрицание истинно. Поэтому b истинно, а elements.All(x => p(x)) должно быть истинным, если elements пусто.

Вот еще один способ подумать об этом. Предикат p является истинным, если для всех x в elements вы не можете найти ни одного, для которого он является ложным. Но если в elements нет элементов, то невозможно найти любые, для которых оно ложно. Таким образом, для пустого набора elements, p истинно для всех x в elements

Теперь, как насчет elements.Any(x => p(x)), когда elements является пустым IEnumerable<T> и p является Predicate<T>, как указано выше? Мы уже знаем, что результат будет ложным, потому что мы знаем, что его отрицание истинно, но пусть рассуждает через него; интуиция ценна. Для того чтобы elements.Any(x => p(x)) быть истинным, должно быть хотя бы одно x в elements, для которого p(x) истинно. Но если в elements не существует x, то невозможно найти x, для которого p(x) истинно. Поэтому elements.Any(x => p(x)) является ложным, если elements пуст.

Наконец, здесь связанное объяснение о том, почему s.StartsWith(String.Empty) истинно, когда s является непустым экземпляром string:

Ответ 2

Если число элементов, возвращающих true, совпадает с количеством всех элементов, верните true. Простой:

Driveway.Cars(a => a.Red).Count() == Driveway.Cars.Count()

Связанное объяснение: Почему "abcd" .StartsWith(") возвращает true?

Ответ 3

"Если коллекция пуста, тест как это, в лучшем случае, undefined. Если моя дорога пуста, я не могу утверждать что все автомобили, припаркованные там, красные".

Да, вы можете.

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

Для тех, кто знаком с понятием SQL, что NULL!= NULL, это неожиданное поведение.

Это причуда SQL (и не совсем верно: NULL = NULL и NULL <> NULL являются undefined, и ни один из них не будет соответствовать ни одному из строк.)

Ответ 4

Я думаю, что это имеет смысл. В логике дополнение FOR ALL НЕ (СУЩЕСТВУЕТ). FOR ALL - это как All(). СУЩЕСТВУЕТ EXIST, как Any().

Итак, IQueryable.All() эквивалентно !IQueryable.Any(). Если ваш IQueryable пуст, то оба возвращают true на основе документа MSDN.

Ответ 5

Any() и All() являются просто реализациями обычных математических операторов ∃ ( "экзистенциальный кватер" или "существует" ) и ∀ ( "универсальный кватер" или "для всех" ).

"Любой" означает, что существует некоторый элемент, для которого предикат является истинным. Для пустой коллекции это было бы неверно.

"Все" означает, что не существует элемента, для которого предикат является ложным. Для пустой коллекции это всегда будет верно.

Ответ 6

Возврат true также логичен. У вас есть два утверждения: "Возьмите автомобиль?" и "Является ли он красным?" Если первое утверждение false, не имеет значения, что второе утверждение есть результат true modus ponens.

Ответ 7

Вы найдете это поведение довольно часто в других областях математики или информатики.

Оператор SUM в Math возвращает 0 (нейтральный элемент +) в случаях, когда диапазоны недействительны (SUM от 0 до -1). Оператор MULTIPYL вернет 1 (нейтральный элемент для умножения).

Теперь, если у вас есть булевы выражения, это очень похоже: нейтральный элемент для OR равен false (a OR false = a), тогда как нейтральный элемент для AND равен true.

Теперь на Linq ANY и ALL: они похожи на это:

ANY = a OR b OR c OR d ...
ALL = a AND b AND c AND d ...

Итак, это поведение - это именно то, что "вы ожидали бы", если у вас есть фон math/cs.

Ответ 8

Поскольку любое предложение для пустого набора было бы пустой истиной.

Ответ 9

False означает, что запрос не возвращал никаких результатов даже без предиката. Это всего лишь неполный способ указать, что Dested размещено, когда я печатал это.

Ответ 10

Это очень похоже на основную концепцию числа ноль. Несмотря на то, что он представляет существование отсутствия, он все еще обладает и представляет ценность. IQueryable.All() должен возвращать true, потому что он успешно вернет всех членов коллекции. Так получилось, что если коллекция пуста, функция не будет возвращать какие-либо элементы, но не потому, что функция не может вернуть никаких членов. Только потому, что не было никаких членов, чтобы вернуться. При этом, почему IQueryable.All() должен испытывать неудачу из-за отсутствия поддержки из коллекции? Он был готов, он смог... он был способен. Мне кажется, что коллекция не смогла задержать их окончание сделки...

http://mathforum.org/dr.math/faq/faq.divideby0.html

Ответ 11

All(x => x.Predicate) противоположно Any(x => !x.Predicate) ( "Все ли автомобили красные?" - это противоположность "Есть ли какие-либо автомобили, которые не красные?" ).

Any(x => !x.Predicate) возвращает false для пустых коллекций (что кажется естественным для общего понимания "любого" ).

Следовательно, All(x => x.Predicate) должен (и делает) возвращать true для пустых коллекций.