.net - new - ¿Por qué IQueryable.All() devuelve verdadero en una colección vacía?
select linq lambda (11)
Así que me encontré con una situación en la que un código de producción estaba fallando precisamente porque un método funcionaba exactamente como se documenta en MSDN . Lástima de mí por no leer la documentación. Sin embargo, todavía estoy rascándome la cabeza por qué se comporta de esta manera, incluso si "por diseño", ya que este comportamiento es exactamente opuesto a lo que habría esperado (y otros comportamientos conocidos) y, por lo tanto, parece violar el principio de menos sorpresa.
El método All()
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 aquí todo bien. Aquí es donde se pone raro. All()
también devuelve true
si la colección está vacía. Esto me parece completamente retroactivo, 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 camino de entrada está vacío, no puedo afirmar que todos los autos estacionados allí son rojos. Con este comportamiento, en un camino vacío todos los autos estacionados allí son de color rojo Y azul Y tablero de ajedrez; todas estas expresiones regresarían verdaderas.
- 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 esperaba y (correctamente) devuelve falso porque no tiene ningún miembro que coincida con el predicado.
Entonces mi pregunta es, ¿por qué All()
comporta de esta manera? ¿Qué problema soluciona? ¿Viola esto el principio de la menor sorpresa?
Etiqueté esta pregunta como .NET 3.5, aunque el comportamiento también se aplica a .NET 4.0.
EDITAR Ok, entiendo el aspecto lógico de esto, como Jason y el resto de ustedes lo han expuesto de manera excelente. Es cierto que una colección vacía es algo así como un caso extremo. Creo que mi pregunta está enraizada en la lucha porque, simplemente porque algo sea lógico , no significa necesariamente que tenga sentido si no estás en el estado mental correcto.
"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í son rojos".
Sí tu puedes.
Para demostrar que estoy equivocado, muéstrame un automóvil en tu entrada vacía que no sea roja.
Para cualquiera que esté familiarizado con la noción SQL de que NULL! = NULL, este es un comportamiento inesperado.
Esta es una peculiaridad de SQL (y no del todo cierto: NULL = NULL
y NULL <> NULL
están definidos, y ninguno coincidirá con ninguna fila).
Si mi camino de entrada está vacío, no puedo afirmar que todos los autos estacionados allí son rojos.
Considera las siguientes afirmaciones.
S1
: mi camino de entrada está vacío.
S2
: Todos los autos estacionados en mi entrada son rojos.
Yo 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 solo cuando S1
es verdadero y S2
es falso. ¿Cuál es la negación de S2
? Es
~S2
: Existe un auto estacionado 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 automóvil estacionado en mi entrada que no está rojo.
La única forma en que S1 ^ ~S2
puede ser verdadero es si tanto S1
como ~S2
son verdaderos. Pero S1
dice que mi camino de entrada está vacío y S2
dice que existe un automóvil en mi entrada. Mi camino de entrada no puede estar vacío y contener un automóvil. Por lo tanto, es imposible que S1
y ~S2
sean verdaderos. Por lo tanto, S1 ^ ~S2
es falso, por lo que su negación S1 => S2
es verdadera.
Por lo tanto, si su entrada está vacía, 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 los elements
están vacíos. 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 una x
en los elements
para la cual !p(x)
es verdadera. Pero los elements
están vacíos, por lo que es imposible encontrar una x
para la cual !p(x)
es verdadera. Por notb
tanto, notb
no puede ser verdadero, entonces debe ser falso. Como notb
es falso, su negación es verdadera. Por lo tanto, b
es verdadero y los elements.All(x => p(x))
debe ser verdadero si los elements
están vacíos.
Aquí hay una forma más de pensar en esto. El predicado p
es verdadero si para todos los elements
x
in no puede encontrar ninguno para el cual sea falso. Pero si no hay elementos en los elements
entonces es imposible encontrar alguno para el cual sea falso. Por lo tanto, para elements
una colección vacía, p
es verdadero para todos los elements
x
en
Ahora, ¿qué pasa con los elements.Any(x => p(x))
IEnumerable<T>
elements.Any(x => p(x))
cuando los elements
son un IEnumerable<T>
vacío IEnumerable<T>
p
es un Predicate<T>
como el anterior? Ya sabemos que el resultado será falso porque sabemos que su negación es verdadera, pero razonemos de todos modos; la intuición es valiosa Para los elements.Any(x => p(x))
que sea verdadero debe tener al menos una x
en los elements
para los cuales p(x)
es verdadera. Pero si no hay ninguna x
en los elements
, es imposible encontrar cualquier x
para la cual p(x)
sea verdadera. Por lo tanto, elements.Any(x => p(x))
Cualquier elements.Any(x => p(x))
es falso si los elements
están vacíos.
Finalmente, aquí hay una explicación relacionada sobre por qué s.StartsWith(String.Empty)
es verdadero cuando s
es una instancia de string
no nula:
Creo que tiene sentido. En lógica, el complemento de FOR ALL NO ES (EXISTE). PARA TODOS es como All()
. ALLÍ EXISTE es como Any()
.
Entonces IQueryable.All()
es equivalente a !IQueryable.Any()
. Si su IQueryable
está vacío, ambos devuelven true según el documento de MSDN.
Encontrará este comportamiento con bastante frecuencia en otras áreas de las matemáticas o la informática.
El operador de SUMA en Matemáticas devolverá 0 (el elemento neutro de +) en los casos donde los rangos no son válidos (la SUM desde 0 hasta -1). El operador MULTIPYL devolverá 1 (elemento neutral para multiplicación).
Ahora bien, si tiene expresiones booleanas, es bastante similar: el elemento neutral para O es false
( a OR false = a
) mientras que el elemento neutral para AND es true
.
Ahora en Linq''s ANY
y ALL
: Son similares a esto:
ANY = a OR b OR c OR d ...
ALL = a AND b AND c AND d ...
Entonces, este comportamiento es exactamente lo que "esperarías" si tienes un fondo matemático / cs.
Es muy similar al concepto básico del número cero. Aunque representa la existencia de la ausencia, aún posee y representa un valor. IQueryable.All () debería devolver verdadero porque devolverá 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 haya podido devolver ningún miembro. Fue solo porque no había miembros para regresar. Dicho esto, ¿por qué debería IQueryable.All () experimentar fallas debido a la falta de soporte de la colección? Estaba dispuesto, fue capaz ... fue capaz. Me parece que la colección no pudo mantener su parte del trato ...
Falso 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 escribía esto.
Porque cualquier proposición para un conjunto vacío sería una verdad vacía .
Si el número de elementos que devuelve true
es el mismo que el número de todos los elementos, devuelve true
. Simple como eso:
Driveway.Cars(a => a.Red).Count() == Driveway.Cars.Count()
Explicación relacionada: ¿Por qué "abcd" .StartsWith ("") devuelve verdadero?
Volverse true
también es lógico. Tienes dos declaraciones: "¿Tienes un auto?" y "¿Es rojo?" Si el primer enunciado es false
, no importa cuál sea el segundo enunciado, el resultado es true
por modus ponens .
All(x => x.Predicate)
es lo opuesto a Any(x => !x.Predicate)
("Are all cars red?" Es lo opuesto a "¿Hay algún coche que no sea rojo?").
Any(x => !x.Predicate)
devuelve false
para colecciones vacías (lo que parece natural para la comprensión común de "cualquiera").
Por lo tanto, All(x => x.Predicate)
debería (y lo hace) devolver true
para colecciones vacías.
Any()
y All()
son simplemente implementaciones de los operadores matemáticos usuales ∃ (el "quatificador existencial" o "existe") y ∀ (el "cuarteador universal" o "para todos").
"Cualquiera" 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 cual el predicado sea falso. Para la colección vacía, esto siempre sería cierto.