LINQ: No Any vs All Don't

272

A menudo quiero verificar si un valor proporcionado coincide con uno en una lista (por ejemplo, al validar):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Recientemente, noté que ReSharper me pedía que simplificara estas consultas para:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Obviamente, esto es lógicamente idéntico, tal vez un poco más legible (si has hecho muchas matemáticas), mi pregunta es: ¿esto resulta en un impacto en el rendimiento?

Parece que debería (es decir, .Any()suena como un cortocircuito, mientras que .All()parece que no), pero no tengo nada que corrobore esto. ¿Alguien tiene un conocimiento más profundo sobre si las consultas resolverán lo mismo o si ReSharper me está desviando?

marca
fuente
66
¿Has intentado desmontar el código de Linq para ver qué está haciendo?
RQDQ
9
En este caso, realmente iría con if (! AcceptValues.Contains (someValue)), pero por supuesto esta no era la pregunta :)
csgero
2
@csgero estoy de acuerdo. Lo anterior fue una simplificación (quizás una simplificación excesiva) de la lógica real.
Mark
1
"Parece que debería (es decir, cualquier () suena como un cortocircuito, mientras que (todo) suena como si no lo hiciera" - No para nadie con intuiciones sonoras. La equivalencia lógica que observas implica que son igualmente cortocircuitables. Un momento de reflexión revela que Todos pueden renunciar tan pronto como se encuentre un caso no calificado.
Jim Balter
3
No estoy universalmente de acuerdo con ReSharper en esto. Escribe trenes razonables de pensamiento. Si desea lanzar una excepción si un elemento necesario es que falta: if (!sequence.Any(v => v == true)). Si desea continuar sólo si todo lo que se ajusta a una cierta especificación: if (sequence.All(v => v < 10)).
Timo

Respuestas:

344

Implementación de Allacuerdo con ILSpy (como en realidad fui y miré, en lugar del "bueno, ese método funciona un poco como ..." podría hacerlo si estuviéramos discutiendo la teoría en lugar del impacto).

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

Implementación de Anyacuerdo a ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

Por supuesto, podría haber alguna diferencia sutil en la IL producida. Pero no, no, no lo hay. La IL es más o menos la misma, pero para la inversión obvia de devolver verdadero en la coincidencia de predicados versus devolver falso en la falta de coincidencia de predicados.

Esto es linq para objetos solo, por supuesto. Es posible que algún otro proveedor de linq trate a uno mucho mejor que el otro, pero si ese fuera el caso, es bastante aleatorio cuál obtuvo la implementación más óptima.

Parece que la regla se reduce únicamente a alguien que siente que if(determineSomethingTrue)es más simple y más legible que if(!determineSomethingFalse). Y para ser justos, creo que tienen un cierto punto en que a menudo me parece if(!someTest)confuso * cuando hay una prueba alternativa de igual verbosidad y complejidad que volvería a ser cierta para la condición sobre la que queremos actuar. Sin embargo, en realidad, personalmente no encuentro nada que favorezca una sobre la otra de las dos alternativas que ofrece, y tal vez me inclinaría ligeramente hacia la primera si el predicado fuera más complicado.

* No es confuso ya que no entiendo, pero confuso como me preocupa que haya alguna razón sutil para la decisión que no entiendo, y se necesitan algunos saltos mentales para darse cuenta de que "no, simplemente decidieron hacerlo de esa manera, espera ¿para qué estaba mirando este bit de código otra vez? ... "

Jon Hanna
fuente
8
No estoy seguro de lo que se hace detrás de las líneas, pero para mí es mucho más legible es: if (no any) que if (all not equal).
VikciaR
49
Hay una GRAN diferencia cuando su enumeración no tiene valores. 'Cualquiera' siempre devolvería FALSO, y 'Todos' siempre devolverían VERDADERO. ¡Entonces decir que uno es el equivalente lógico del otro no es del todo cierto!
Arnaud
44
@Arnaud Anyvolverá falsey, por !Anylo tanto , volverá true, por lo que son idénticos.
Jon Hanna
11
@Arnaud No hay ninguna persona que haya comentado que haya dicho que Any and All son lógicamente equivalentes. O para decirlo de otra manera, todas las personas que comentaron no dijeron que Any y All son lógicamente equivalentes. La equivalencia es entre! Any (predicado) y All (! Predicate).
Jim Balter
77
@MacsDickinson eso no es una diferencia en absoluto, porque no estás comparando predicados opuestos. El equivalente a !test.Any(x => x.Key == 3 && x.Value == 1)esos usos Alles test.All(x => !(x.Key == 3 && x.Value == 1))(que de hecho es equivalente a test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Puede encontrar que estos métodos de extensión hacen que su código sea más legible:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

Ahora en lugar de tu original

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

tu puedes decir

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
fuente
66
Gracias. Ya estaba pensando en implementarlos en nuestra biblioteca de commons, pero aún no he decidido si es una buena idea. Estoy de acuerdo en que hacen que el código sea más legible, pero me preocupa que no agreguen suficiente valor.
Mark
2
Busqué Ninguno y no lo encontré. Es mucho más legible.
Rhyous
Tuve que agregar cheques nulos: return source == null || ! source.Any (predicado);
Rhyous
27

Ambos tendrían un rendimiento idéntico porque ambos detienen la enumeración después de que se puede determinar el resultado: Any()en el primer elemento, el predicado pasado se evalúa truey All()en el primer elemento al que se evalúa el predicado false.

Vidrio roto
fuente
21

All cortocircuitos en el primer no partido, por lo que no es un problema.

Un área de sutileza es que

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

Es verdad. Todos los elementos de la secuencia son pares.

Para más información sobre este método, consulte la documentación de Enumerable.All .

Anthony Pegram
fuente
12
Sí, pero también bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)es cierto.
Jon Hanna
1
@Jon semánticamente ninguno! = Todos. Entonces, semánticamente, no tiene ninguno o todos, pero en el caso de .All () none es solo un subconjunto de todas las colecciones que devuelven verdadero para todos y esa discrepancia puede generar errores si no lo sabe. +1 para ese Anthony
Rune FS
@RuneFS no lo sigo. Semántica y lógicamente "ninguno donde no es cierto que ..." es, de hecho, lo mismo que "todo donde es cierto eso". Por ejemplo, "¿dónde ninguno de los proyectos aceptados de nuestra empresa?" siempre tendrá la misma respuesta que "¿dónde están todos los proyectos aceptados de otras compañías?" ...
Jon Hanna
... Ahora, es cierto que puede tener errores al suponer que "todos los elementos son ..." significa que hay al menos un elemento que es al menos un elemento que cumple la prueba, ya que "todos los elementos ... "siempre es cierto para el conjunto vacío, no lo cuestiono en absoluto. Sin embargo, agregué que el mismo problema puede suceder suponiendo que "ninguno de los elementos ..." significa que al menos uno de los elementos no cumple la prueba, ya que "ninguno de los elementos ..." también es siempre cierto para el conjunto vacío . No es que no esté de acuerdo con el punto de Anthony, es que creo que también es válido para la otra de las dos construcciones en discusión.
Jon Hanna
@ Jon, estás hablando de lógica y yo estoy hablando de lingüística. El cerebro humano no puede procesar un negativo (antes de procesar el positivo en ese punto, entonces puede negarlo), por lo que en ese sentido hay una gran diferencia entre los dos. Eso no hace que la lógica que propongas sea incorrecta
Rune FS
8

All()determina si todos los elementos de una secuencia satisfacen una condición.
Any()determina si algún elemento de una secuencia cumple la condición.

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true
emy
fuente
7

De acuerdo a este enlace

Cualquiera: comprueba al menos una coincidencia

Todos: comprueba que todos coinciden

rcarvalhoxavier
fuente
1
tienes razón pero se detienen al mismo tiempo para una colección determinada. Todo se rompe cuando la condición falla y Cualquier interrupción cuando coincide con su predicado. Así que técnicamente no es diferente, excepto de manera escénica
WPFKK
6

Como otras respuestas han cubierto bien: no se trata de rendimiento, se trata de claridad.

Hay un amplio soporte para sus dos opciones:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Pero creo que esto podría lograr un apoyo más amplio :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

Simplemente calcular el booleano (y nombrarlo) antes de negar cualquier cosa me aclara mucho esto.

Michael Haren
fuente
3

Si echas un vistazo a la fuente Enumerable , verás que la implementación de Anyy Allestá bastante cerca:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

No hay forma de que un método sea significativamente más rápido que el otro, ya que la única diferencia radica en una negación booleana, por lo que prefiere la legibilidad sobre la ganancia de rendimiento falso.

Thomas Ayoub
fuente