¿Por qué Enumerable.All devuelve verdadero para una secuencia vacía?

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

El código crea una colección vacía de cadena, luego trata de determinar si todos los elementos en la colección son “ABC”. Si lo ejecuta, b será verdadero.

Pero la colección ni siquiera tiene elementos, y mucho menos elementos que sean iguales a “ABC”.

¿Es esto un error, o hay una explicación razonable?

Ciertamente no es un error. Se está comportando exactamente como está documentado :

verdadero si cada elemento de la secuencia fuente pasa la prueba en el predicado especificado, o si la secuencia está vacía ; de lo contrario, falso.

Ahora puede discutir si debe funcionar de esa manera (me parece bien, cada elemento de la secuencia se ajusta al predicado), pero lo primero que debe verificar antes de preguntar si algo es un error es la documentación. (Es lo primero que debe verificar tan pronto como un método se comporte de una manera diferente a la esperada).

Todo requiere que el predicado sea verdadero para todos los elementos de la secuencia. Esto se establece explícitamente en la documentación. También es lo único que tiene sentido si piensa que All es como un resultado lógico y entre los predicados para cada elemento. Lo “verdadero” que está obteniendo para la secuencia vacía es el elemento de identidad de la operación y. Del mismo modo, lo falso que obtiene de Cualquiera para la secuencia vacía es la identidad para lógica o.

Si piensas en “todos” como “no hay elementos en la secuencia que no lo son”, esto podría tener más sentido.

Es true , ya que nada (ninguna condición) lo hace false .

Los documentos probablemente lo explican. (Jon Skeet también mencionó algo hace unos años)

Lo mismo ocurre con Any (el opuesto de All ) devuelve false para conjuntos vacíos.

Editar:

Puedes imaginar que All se implementará semánticamente de la misma manera que:

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

El método recorre todos los elementos hasta que encuentra uno que no satisface la condición o no encuentra ninguno que falle. Si ninguno falla, se devuelve verdadero.

Entonces, si no hay elementos, se devuelve verdadero (ya que no hubo ninguno que falló)

La mayoría de las respuestas aquí parecen ir en la línea de “porque así es como se define”. Pero también hay una razón lógica por la cual se define de esta manera.

Al definir una función, desea que su función sea lo más general posible, de modo que se pueda aplicar al mayor número posible de casos. Digamos, por ejemplo, que quiero definir la función Sum , que devuelve la sum de todos los números en una lista. ¿Qué debería devolver cuando la lista está vacía? Si devuelve un número arbitrario x , debe definir la función como:

  1. Función que devuelve la sum de todos los números en la lista dada, o x si la lista está vacía.

Pero si x es cero, también puede definirlo como

  1. Función que devuelve x más los números dados.

Tenga en cuenta que la definición 2 implica la definición 1, pero 1 no implica 2 cuando x no es cero, lo que en sí mismo es razón suficiente para elegir 2 sobre 1. Pero también la nota 2 es más elegante y, por derecho propio, más general que 1 . Es como colocar un foco de luz más lejos para que aligere un área más grande. Mucho más grande en realidad. Yo no soy un matemático, pero estoy seguro de que encontrarán un montón de conexiones entre la definición 2 y otros conceptos matemáticos, pero no tantas relacionadas con la definición 1 cuando x no es cero.

En general, puede, y lo más probable es que desee devolver el elemento de identidad (el que deja el otro operando sin cambios) siempre que tenga una función que aplica un operador binario sobre un conjunto de elementos y el conjunto está vacío. Esta es la misma razón por la que una función de Product devolverá 1 cuando la lista esté vacía (tenga en cuenta que podría reemplazar ” x plus” por “una vez” en la definición 2). Y es la misma razón por la que All (que puede considerarse como la aplicación repetida del operador AND lógico) volverá true cuando la lista está vacía ( p && true es equivalente a p ), y el mismo motivo Any (el operador OR) devolverá false .

Mantener la implementación a un lado. ¿Realmente importa si es verdad? Vea si tiene algún código que itere sobre el enumerable y ejecute algún código. si All () es verdadero, entonces ese código aún no se ejecutará ya que el enumerable no tiene ningún elemento.

 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. 

Aquí hay una extensión que puede hacer lo que OP quería hacer:

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

… y como otros ya han señalado, esto no es un error, sino un comportamiento previsto bien documentado.

Una solución alternativa si uno no desea escribir una nueva extensión es:

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

PD: ¡Lo anterior no funciona si busca el valor predeterminado en sí mismo! (Que para cadenas sería nulo.) En tales casos se vuelve menos elegante con algo similar a:

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

(Recomiendo el enfoque de la extensión en general, pero especialmente para tales casos).