¿Por qué IQueryable.All () devuelve true en una colección vacía?


Así que hoy me encontré con una situación en la que algún código de producción estaba fallando precisamente porque un método se realizó exactamente como documentado en MSDN. Debería darme vergüenza por no leer la documentación. Sin embargo, todavía me estoy rascando la cabeza en cuanto a por qué se comporta de esta manera, incluso si "por diseño", ya que este comportamiento es exactamente opuesto a lo que hubiera esperado (y otros comportamientos conocidos) y por lo tanto parece violar el principio de menor sorpresa.

El método All() le permite suministrar un predicado (como una expresión lambda) para probar un IQueryable, devolviendo un valor booleano que indica si todos los miembros de la colección coinciden con la prueba. Hasta ahora todo bien. Aquí es donde se pone raro. All() también devuelve true si la colección está vacía. Esto me parece completamente al revés, por las siguientes razones:

  • Si la colección está vacía, una prueba como esta es, en el mejor de los casos, indefinida. Si mi entrada está vacía, no puedo afirmar que todos los autos estacionados allí estén rojo. Con este comportamiento, en un camino de entrada vacío todos los coches estacionados allí son rojos Y azules Y tablero de ajedrez - todas estas expresiones devolverían verdadero.
  • Para cualquiera que esté familiarizado con la noción SQL de que NULL != NULL, este es un comportamiento inesperado.
  • El método Any() se comporta como se espera, y (correctamente) devuelve false porque no tiene ningún miembro que coincida con el predicado.

Así que mi pregunta es, ¿por qué All() se comporta de esta manera? ¿Qué problema resuelve? Hacer esto viola el principio de la menor sorpresa?

Etiqueté esta pregunta como. NET 3.5, aunque el comportamiento también se aplica a. NET 4.0.

EDIT Ok, así que entiendo el aspecto lógico de esto, tan excelentemente expuesto por Jason y el resto de ustedes. Es cierto que una colección vacía es algo así como un caso de borde. Supongo que mi pregunta tiene sus raíces en la lucha que, solo porque algo es lógico no significa que necesariamente tenga sentido si eres no en el estado mental correcto.

Author: GalacticCowboy, 2010-02-03

11 answers

Si mi entrada está vacía, no puedo afirmar que todos los autos estacionados allí sean rojos.

Considere las siguientes afirmaciones.

S1: Mi entrada está vacía.

S2: Todos los autos estacionados en mi entrada son rojos.

Afirmo que S1 implica S2. Es decir, la declaración S1 => S2 es verdadera. Haré esto mostrando que su negación es falsa. En este caso, la negación de S1 => S2 es S1 ^ ~S2; esto es porque S1 => S2 es falso solamente cuando S1 es verdadero y S2 es falso. ¿Cuál es la negación de S2? Es

~S2: Hay un coche aparcado en mi entrada que no es rojo.

¿Cuál es el valor de verdad de S1 ^ ~S2? Vamos a escribirlo

S1 ^ ~S2: Mi camino de entrada está vacío y existe un coche aparcado en mi camino de entrada que no es rojo.

La única manera en que S1 ^ ~S2 puede ser verdadera es si ambos S1 y ~S2 son verdaderos. Pero S1 dice que mi entrada está vacía y S2 dice que hay un coche en mi entrada. Mi camino de entrada no puede estar vacío y contener un coche. Por lo tanto, es imposible que S1 y ~S2 ambos sean verdaderos. Por lo tanto, S1 ^ ~S2 es falso por lo que su negación S1 => S2 es verdadera.

Por lo tanto, si su camino de entrada está vacío, puede afirmar que todos los autos estacionados allí son rojos.

Así que ahora consideremos un IEnumerable<T> elements y un Predicate<T> p. Supongamos que elements está vacío. Queremos descubrir el valor de

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

Consideremos su negación

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

Para que notb sea verdadero, debe haber al menos uno x en elements para el cual !p(x) sea verdadero. Pero elements está vacío, por lo que es imposible encontrar un x para el cual !p(x) es verdadero. Por lo tanto notb no puede ser verdad por lo que debe ser falso. Puesto que notb es falso, su negación es verdadera. Por lo tanto b es verdadero y elements.All(x => p(x)) debe ser verdadero si elements está vacío.

Aquí hay una forma más de pensar en esto. El predicado p es verdadero si para todos x en elements no puedes encontrar ningún para el que sea falso. Pero si no hay elementos en elements entonces es imposible encontrar cualquier para el que sea falso. Por lo tanto, para una colección vacíaelements, p es cierto para todos x in elements

Ahora, ¿qué pasa con elements.Any(x => p(x)) cuando elements es un IEnumerable<T> vacío y p es un Predicate<T> como antes? Ya sabemos que el resultado será falso porque sabemos que su negación es verdadera, pero razonemos a través de él de todos modos; la intuición es valiosa. Para que elements.Any(x => p(x)) sea verdadero debe haber al menos uno x en elements para el cual p(x) sea verdadero. Pero si no hay cualquier x en elements es imposible encontrar cualquier x para que p(x) es cierto. Por lo tanto, elements.Any(x => p(x)) es false si elements está vacío.

Finalmente, aquí hay una explicación relacionada sobre por qué s.StartsWith(String.Empty) es verdadera cuando s es una instancia no nula de string:

 40
Author: jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:09:17

Si el número de los elementos que devuelven true es el mismo que el número de todos los elementos, entonces devuelva true. Así de simple:

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

Explicación relacionada: Por qué "abcd".startsWith("") devuelve true?

 8
Author: Dested,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:53:55

" Si la colección está vacía, una prueba como si esto fuera, en el mejor de los casos, indefinido. Si mi camino está vacío, no puedo afirmar que todos los autos estacionados allí son rojos."

Sí puedes.

Para probar que me equivoco, muéstrame un auto en tu camino vacío que no sea rojo.

Para cualquiera que esté familiarizado con la noción SQL de que NULL != NULL, este es un comportamiento inesperado.

Esto es una peculiaridad de SQL (y no del todo cierto: NULL = NULL y NULL <> NULL son ambos indefinidos, y ninguno coincidirá con cualquier fila.)

 5
Author: finnw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 20:51:17

Any() y All() son solo implementaciones de los operadores matemáticos usuales ∃ (el "quatifier existencial" o "existe") y ∀ (el "quatifier universal" o "para todos").

"Any" significa que existe algún elemento para el cual el predicado es verdadero. Para la colección vacía, esto sería falso.

"Todo" significa que no existe ningún elemento para el que el predicado sea falso. Para la colección vacía, esto siempre sería cierto.

 4
Author: Jeffrey L Whitledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 21:13:09

Creo que tiene sentido. En lógica, el complemento de PARA TODOS NO es (EXISTE). PORQUE TODO es como All(). EXISTE es como Any().

Así que IQueryable.All() es equivalente a !IQueryable.Any(). Si su IQueryable está vacío, entonces ambos devuelve true basado en MSDN doc.

 4
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-01 17:09:43

Usted encontrará este comportamiento muy a menudo en otras áreas de las matemáticas o ciencias de la computación.

El operador SUMA en Matemáticas devolverá 0 (el elemento neutro de +) en los casos en que los rangos no son válidos (la SUMA de 0 hasta -1). El operador MULTIPYL devolverá 1 (elemento neutro para la multiplicación).

Ahora bien, si usted tiene expresiones booleanas, es bastante similar: El elemento neutral para OR es false (a OR false = a) mientras que el elemento neutro para Y es true.

Ahora en Linq ANY y ALL: Son similares a esto:

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

Así que este comportamiento es justo lo que "esperarías" si tienes un fondo de matemáticas/cs.

 1
Author: Marcel Jackwerth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 21:00:48

Devolver true también es lógico. Tiene dos declaraciones: " ¿Tiene un auto?" y " ¿Es rojo?" Si la primera instrucción es false, no importa cuál sea la segunda instrucción, el resultado es true por modus ponens.

 1
Author: codekaizen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 21:03:51

Porque cualquier proposición a un conjunto vacío sería una verdad vacua.

 1
Author: kerem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-28 13:10:44

False significa que la consulta no devolvió ningún resultado incluso sin el predicado. Lo cual es solo una forma incompleta de indicar lo que Dested publicó mientras estaba escribiendo esto.

 0
Author: Wade,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 20:57:35

Es muy similar al concepto básico del número cero. Aunque representa la existencia de la ausencia, todavía posee y representa un valor. IQueryable.All () debería devolver true, porque devolverá con éxito todos los miembros de la colección. Sucede que si la colección está vacía, la función no devolverá ningún miembro, pero no porque la función no podría devolver ningún miembro. Fue solo porque no había miembros que regresaran. Que dicho esto, ¿por qué debería IQueryable?All() tiene que experimentar un fallo debido a la falta de soporte de la colección? Estaba dispuesto, era able...it era capaz. Me parece que la colección no pudo cumplir su parte del trato...

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

 0
Author: Neil T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-03 21:05:17

All(x => x.Predicate) is the opposite of Any(x => !x.Predicate) ("Are all cars red?"es lo contrario de" ¿Hay algún coche que no sea rojo?").

Any(x => !x.Predicate) devuelve false para colecciones vacías (que aparece natural para el entendimiento común de "any").

Por lo tanto, All(x => x.Predicate) debe (y devuelve) true para colecciones vacías.

 0
Author: LWChris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-07 23:42:41