El dilema

A veces necesitas ejecutar alguna acción para comprobar si un elemento no está presente en una colección, por ejemplo, comprobar si un cliente está en la lista de clientes antes de añadirlo a ella.

Con .NET hay varias maneras de hacer esto: Por ejemplo si tu colección es una List puedes usar Exists(), o si quieres comprobar la presencia de la instancia de ese mismo objeto, puedes usar Contains(). Usando Linq, también puedes hacerlo con Any() y All().

En nuestro artículo de hoy, vamos a ver si es mejor usar !.Any() o .All()

El escenario

Vamos a utilizar una lista de clientes y un nuevo cliente que estoy a punto de añadir a la lista, de esta manera:

Collection<Customer> Customers = MyRepo.GetAllCustomers();
Customer myCustomer = CustomerMappedFromViewModel();

Antes de añadir el nuevo cliente, quiero ver si ya hay actualmente otro cliente con la misma Id antes de añadir el nuevo, para evitar duplicados. Podemos resolver esto con cualquiera de las dos opciones siguientes:

Usando !Any para comprobar que la Id del cliente no está en ninguno de los clientes de la lista Customers:

if (!Customers.Any(cust => cust.Id == myCustomer.Id)) { 
    Customers.Add(myCustomer); 
}

O usando All para comprobar que todas las Customers.Id son diferentes a la que queremos añadir:

if (Customers.All(cust => cust.Id != myCustomer.Id)) { 
    Customers.Add(myCustomer); 
}

La pregunta

Viendo estas dos opciones, ¿cual crees que es mejor usar?

La mayoría de la gente contestará que la primera. Normalmente esto ocurre porque las palabras Any y All engañan a nuestro cerebro y nos hacen pensar que All iterará “todos” los elementos de la colección, mientras que Any dejará de iterar cuando “algún” elemento cumpla el predicado.

La realidad, en cambio, es otra. Adentrémonos más en el tema.

Excavando en las sombras de .NET

Usando Reflector para ver el código de Linq para Any y All podemos ver esto:

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;
}
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;
}

Sorpresa! Las dos implementaciones hacen EXACTAMENTE lo mismo!. La única diferencia es que Any dejará de iterar cuando el predicado cumpla y devolverá true, mientas que All dejará de iterar cuando el predicado no cumpla y devolverá false. Así que, el tiempo que tardarán Any y All, será casi el mismo (dependiendo de los valores).

Sabiendo esto, volvemos a la casilla nº 1: Si hacen casi lo mismo, ¿cual debería usar?

And the winner is…

Siguiendo las buenas prácticas de desarrollo, la opción correcta debería ser la 2 (All)

if (Customers.All(cust => cust.Id != myCustomer.Id)) {
    Customers.Add(myCustomer); 
}

Según dictan éstas buenas prácticas, deberíamos siempre hacer que nuestros bloques “if” comprobasen predicados positivos en vez de negarlos. Por ejemplo:

if (PasaEsto()) {
    HazEsto();
}
else {
    HazLoOtro();
}

Es correcto, mientras que esto:

if (!PasaEsto()) {
    HazLoOtro();
}
else {
    HazEsto();
}

Es incorrecto!

Bajo estas circunstancias, usar .All en este caso es mejor que usar !.Any

Saludos y buen código!

Follow me

Joan Vilariño

Senior .NET Developer at Ohpen
Follow me