domingo, 24 de mayo de 2015

Operadores de Elemento, First, Single, LastOrDefault ... etc.




Los operadores de elemento son todos aquellos métodos extensores dentro de la librería de LinQ, que devuelven una única pieza o el valor por defecto de su tipo a partir de una secuencia de datos IEnumerable<TSource>.


Estos operadores suelen estar compuestos por su definición simple y una variación ‘OrDefault’. Esta variación ‘OrDefault’, viene a significar, que en caso de no cumplirse el criterio de búsqueda, el operador no lanzará ninguna excepción y devolverá el valor por defecto del tipo al que pertenece la secuencia. Este valor por defecto es el que devolvería el operador default(T), que ya estudiamos con anterioridad.













Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.



ElementAt y ElementAtOrDefault, son unos operadores que devuelven el elemento situado en la posición que marca su parámetro. Este parámetro es un int de índice 0.


En caso de indicar un índice negativo, o un índice superior al nº de elementos que contiene la secuencia, se lanzará una excepción de tipo ArgumentOutOfRangeException, siempre y cuando estemos utilizando el operador ElementAt, si elegimos la opción de ElementAtOrDefault, el método nos devolverá el valor por defecto default(T) del tipo de datos que esté compuesto la colección.

Estas son las firmas de sus métodos extensores:

public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index);
 
public static TSource ElementAtOrDefault<TSource>(this IEnumerable<TSource> source, int index);


A simple vista puede parecer que no tienen mucha utilidad, ya que normalmente solemos trabajar con colecciones que implementan la interfaz IList y está activa el uso de indexadores a través de los corchetes ‘[]’, pero cuando trabajamos directamente con IEnumerables<TSource>, y con ejecución diferida, no tenemos disponible esta posibilidad y este operador se vuelve prácticamente indispensable.

static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    // Como se puede observar no llamo ni a ToList() ni a ToArray() etc, por lo que la ejecución
    // me devuelve un IEnumarable<Automovil>
    var cochesQueEmpiezanPorC = coches.Where(c => c.Modelo.StartsWith("c", StringComparison.CurrentCultureIgnoreCase));
 
    for (int i = 0; i < cochesQueEmpiezanPorC.Count(); i++)
    {
        var cocheActual = cochesQueEmpiezanPorC.ElementAt(i);
 
        Console.WriteLine("{0} - {1}", cocheActual.Marca, cocheActual.Modelo);
    }
 
    Console.Read();
}


Como podemos observar, a la hora de filtrar la operación, no hacemos uso del operador ToList(), que obligaría a realizar la ejecución en el momento, por lo que efectuaremos ejecución diferida y el filtrado no se ejecutará hasta que estemos dentro del bucle For. El tipo de devolución será un IEnumerable<Automovil> y este tipo no tiene posibilidad de indexadores, y el único medio de acceder a sus elementos por índice sería mediante el operador ElementAt.



First y FirstOrDefault

Estos dos operadores devuelven el primer elemento de una colección.


Tienen 2 sobrecargas, una en la que no recibe ningún tipo de parámetro y otra en la que recibe un parámetro de tipo Func<TSource, bool> que realizará un filtrado inicial de los datos, y se quedará con el primer elemento del resultado del filtrado:

Estas son las firmas de sus métodos extensores:


public static TSurce First<TSource>(this IEnumerable<TSource> source);
public static TSurce First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
 
public static TSurce FirstOrDefault<TSource>(this IEnumerable<TSource> source);
public static TSurce FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);


En caso de que la colección esté vacía, si utilizamos el método First, se lanzará una excepción del tipo InvalidOperationException. Para evitarlo, como en los casos anteriores utilizar FirstOrDefault.

static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };
 
    var primerCoche = coches.First();
 
    Console.WriteLine("El primer coche es {0} - {1}", primerCoche.Marca, primerCoche.Modelo);
 
    /// Aleatoriamente, realizo una limpia de los elementos de la colección
    if (DateTime.Now.Second % 2 == 0)
    {
        Console.WriteLine("Limpiamos la colección de coches");
        coches.Clear();
    }
    else
    {
        Console.WriteLine("NO se ha limpiado la colección de coches");
    }
 
    var primerCoche2 = coches.FirstOrDefault();
 
    if (primerCoche2 == default(Automovil))
    {
        Console.WriteLine("La lista de coches está vacía");
    }
    else
    {
        Console.WriteLine("El primer coche aleatorio es {0} - {1}", primerCoche.Marca, primerCoche.Modelo);
    }
 
    Console.Read();
}

Muy sencillo, al principio seleccionamos el primer elemento con el operador First, ya que estamos seguros que la lista contiene elementos. Después, hacemos algo pseudo aleatorio, comprobando si el segundo actual de la fecha del sistema es par, en ese caso, limpiamos la colección, en caso contrario la dejamos igual. Para este caso estamos obligados a utilizar FirstOrDefault, ya que no tenemos la certeza que nuestra secuencia contenga elementos. Para acabar, impresión en la consola y poco más.


Resultado de la ejecución cuando el segundo es par:















Ahora la otra opción:















NOTA:

En esta parte tengo que hacer un apunte de un buen uso de LinQ, poniendo un ejemplo que normalmente solemos realizar para buscar un elemento en concreto dentro de una colección:


var cocheClio = coches.Where(c => c.Modelo == "Clio").FirstOrDefault();
 
var cocheClio = (from c in coches
                 where c.Modelo == "Clio"
                 select c).FirstOrDefault();


Realizados un filtrado por un criterio y después llamamos a FirstOrDefault, para quedarnos con la primera pieza. Esta acción no es todo lo óptima que debiera, y no utiliza toda la potencia de LinQ, ya que primero efectúa el filtrado y luego se queda con la primera operación. Para elaborar esto, utilizamos la segunda sobrecarga de First y FirstOrDefault, que confecciona todo esta tarea, escribiendo menos código y trabajando a bajo nivel. Así sería:


var cocheClio = coches.FirstOrDefault(c => c.Modelo == "Clio");




Last y LastOrDefault


Pues para no soltar mucho más rollo, ni alargar esto por alargar, de los operadores Last y LastOrDefault, solo hay que añadir que son exactamente iguales a los dos anteriores (First y FirstOrDefault) pero en vez de devolver el primer elemento, devolviendo el último.




Single y SingleOrDefault

Los operadores Single y SingleOrDefault, son bastante más restrictivos que los anteriores y obligan a que la secuencia o el resultado del predicado facilitado a la secuencia sea de un único elemento. En el caso de Single, si la colección o el predicado de resultado devuelven un número de elementos diferente a 1, ósea, 0, 2, 3, …, n, se lanzará un InvalidOperationException. En el caso de SingleOrDefault, si la colección o el resultado del predicado retornan 0 elementos, este como en los casos anteriores de ‘OrDefault’ reintegrará el valor por defecto default(T) de TSource, si el resultado es mayor de 1, el proceso será el mismo que con su hermano Single y lanzará un InvalidOperationException.

Estos operadores también tienen 2 sobrecargas cada uno, una inicial que no recibe ningún parámetro y otra que recibe como viene siendo normal en estos casos un Func<TSource, bool> llamado Predicate.


Estas son las firmas de sus métodos extensores:


public static TSource Single<TSource>(this IEnumerable<TSource> source)
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source)
public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)


Vamos ahora a ver un ejemplo en código:

static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };

    /// 1.- Single 
        // Lanzaría un InvalidOperationException ya que hay más de 1 coche
    var CocheSencillo = coches.Single();
    // Funcionaría correctamente
    var cocheSencilloFiltrado = coches.Single(c => c.Modelo == "Clio");
    /// 2.- SingleOrDefault
        // Lanzaría un InvalidOperationException ya que hay más de 1 coche
    var cocheSencilloPorDefecto = coches.SingleOrDefault();
        // Devolvería default(Automovil) porque no hay ningún coche modelo 'Fiesta'
    var cocheSencilloPorDefectoFiltrado = coches.SingleOrDefault(c => c.Modelo == "Fiesta");

    Console.Read();
}





DefaultIfEmpty  

Este operador, nos permite añadir un elemento a nuestra secuencia, en caso de que esta esté vacía, vamos que posea 0 items. Tiene 2 sobrecargas, una que recibe parámetros y que añadiría un elemento del tipo default(TSource), y otra que nos permite añadir el valor que contendrá por defecto.


Estas son las firmas de sus sobrecargas:


public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source)
public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source, TSource defaultValue)


Vamos a ver lo que nos gusta, el código:

static void Main(string[] args)
{
    var coches = new List<Automovil>
    {
        new Automovil { Marca = "Renault", Modelo = "Clio"  , Carburante = TipoCarburante.Gasolina, Color = Colors.Green },
        new Automovil { Marca = "Citroen", Modelo = "C3"    , Carburante = TipoCarburante.Diesel  , Color = Colors.Gray  },
        new Automovil { Marca = "Renault", Modelo = "Laguna", Carburante = TipoCarburante.Diesel  , Color = Colors.Black },
        new Automovil { Marca = "Renault", Modelo = "Megane", Carburante = TipoCarburante.Gasolina, Color = Colors.Blue  },
        new Automovil { Marca = "Citroen", Modelo = "C4"    , Carburante = TipoCarburante.Gasolina, Color = Colors.Black }
    };

    coches.Clear();

    /// 1.- Sobrecarga Simple
    var cochesPorDefectosimple = coches.DefaultIfEmpty();

    Console.WriteLine("El resultado de la primera sobrecarga es:");
    for (int i = 0; i < cochesPorDefectosimple.Count(); i++)
    {
        var coche = cochesPorDefectosimple.ElementAt(i);

        Console.WriteLine("{0}.- el valor del coche es {1}", i + 1, (coche == null ? "null" : "Coche instanciado"));
    }

    /// 2.- Sobrecargar con valor por defecto
    var cochesPorDefectoValorPorDefecto = coches.DefaultIfEmpty(new Automovil
                                                                {
                                                                    Marca  = "Sin Marca",
                                                                    Modelo = "Sin Modelo"
                                                                });

    Console.WriteLine("El resultado de la segunda sobrecarga es:");
    for (int i = 0; i < cochesPorDefectoValorPorDefecto.Count(); i++)
    {
        var coche = cochesPorDefectoValorPorDefecto.ElementAt(i);

        Console.WriteLine("{0}.- el valor del coche es {1} - {2}", i + 1, coche.Marca, coche.Modelo);
    }



    Console.Read();
}


Resultado:




Como podemos observar, en cada una de las ejecuciones de las dos sobrecargas, las secuencias de resultado solo tienen un elemento. En el primer caso, con el valor ‘null’, que es el valor por defecto del tipo Automovil y en el segundo, un valor por defecto personalizado que le indicamos en el parámetro.