Почему IQueryable.All () возвращает true в пустой коллекции? - PullRequest
45 голосов
/ 03 февраля 2010

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

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

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

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

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

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

Ответы [ 11 ]

42 голосов
/ 04 февраля 2010

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

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

S1: Моя дорога пуста.

S2: Все машины, припаркованные на моей дороге, красные.

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

~S2: На моей дороге припаркована машина, которая не красного цвета.

Какое значение истинности S1 ^ ~S2? Давайте выпишем это

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

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

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

Итак, давайте рассмотрим IEnumerable<T> elements и 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 равен true, если для all x в elements вы не можете найти любой , для которого он равен false. Но если в 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) является истинным. Но если нет любого x в elements, невозможно найти любого x, для которого p(x) истинно. Следовательно, elements.Any(x => p(x)) ложно, если elements пусто.

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

8 голосов
/ 03 февраля 2010

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

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

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

4 голосов
/ 03 февраля 2010

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

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

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

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

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

3 голосов
/ 04 февраля 2010

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

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

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

3 голосов
/ 03 февраля 2010

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

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

1 голос
/ 28 апреля 2015

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

1 голос
/ 04 февраля 2010

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

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

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

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

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

Так что это поведение именно то, что "вы ожидаете", если у вас есть математика / CS фон.

1 голос
/ 03 февраля 2010

Возвращение true тоже логично. У вас есть два утверждения: «Есть машина?» и «Это красное?» Если первое утверждение false, то не имеет значения, какое второе утверждение результат true по modus ponens .

0 голосов
/ 08 сентября 2016

All(x => x.Predicate) является противоположностью Any(x => !x.Predicate) («Все ли машины красные?» Является противоположностью «Есть ли машины, которые не красные?»).

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

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

0 голосов
/ 03 февраля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...