lunes, 16 de mayo de 2016

Let




Una entrada que pudiera ser no merecedora de un espacio para ella sola. La cláusula Let, tiene una serie de virtudes que van desde una mejora notable en la lectura y comprensión del código, hasta un incremento en el rendimiento por la reducción de llamadas redundantes.


La única nota negativa, pero no menos importante, es que esta cláusula, solo puede utilizarse con sintaxis de consulta (azúcar sintáctico), por lo que nuestras queridas Lambdas se quedan fuera.









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



A nivel general la cláusula Let nos permite crear una variable local a nivel de consulta. El mecanismo es exactamente el mismo al que utilizamos cuando generamos una variable a nivel local dentro de un método o de un bucle.

Veamos a ver un ejemplo, que es bastante más representativo.


Esta será la clase o entidad que utilizaremos para nuestros ejemplos:







public class Tienda
{
    public string Nombre { get; set; }
    public decimal Ventas { get; set; }
    public DateTime FechaApertura { get; set; }
 
 
 
    public override string ToString() => $"Nombre:{Nombre} - Ventas:{Ventas} - FechaAp:{FechaApertura}";
 
    public static IEnumerable<Tienda> GenerarTiendas()
    {
        return new List<Tienda>()
        {
            new Tienda { Nombre = "La tienda magica"       , Ventas = 1000000m, FechaApertura = new DateTime(1998, 1,  1) },
            new Tienda { Nombre = "La peor  de las tiendas", Ventas =      10m, FechaApertura = new DateTime(2001, 2, 25) },
            new Tienda { Nombre = "La tienda exotérica"    , Ventas = 2000000m, FechaApertura = new DateTime(1990, 9,  1) },
            new Tienda { Nombre = "Otra tienda mas"        , Ventas = 8000000m, FechaApertura = new DateTime(2015, 3,  2) }
        };
    }
 
}



Este es el primer ejemplo:



static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 3 'a' en su nombre, que su aniversario cae en viernes este año
    /// y que tienen más de 990.000 de ventas
    var tiendasFiltro = from t in tiendas
                        where t.Nombre.Where(n => n == 'a').Count() > 3
                        && new DateTime(DateTime.Today.Year, t.FechaApertura.Month, t.FechaApertura.Day).DayOfWeek == DayOfWeek.Friday
                        && t.Ventas > 990000m
                        select t;
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine(tienda);
    }
 
 
    Console.Read();
}



Como vemos la consulta tiene mucha parte de lógica de negocio y se hace complicada su lectura. Esto lo podríamos arreglar creando un método local para cada una de las comparaciones, pero la cláusula Let, nos va a ahorrar este trabajo y nos va a dar la comprensión y lectura de código que buscamos:





static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 3 'a' en su nombre, que su aniversario cae en viernes este año
    /// y que tienen más de 990.000 de ventas
    var tiendasFiltro = from t in tiendas 
 
                        let letrasA      = t.Nombre.Where(n => n == 'a').Count()
                        let diaAñoActual = new DateTime(DateTime.Today.Year, t.FechaApertura.Month, t.FechaApertura.Day).DayOfWeek
 
                        where letrasA > 3
                        && diaAñoActual == DayOfWeek.Friday
                        && t.Ventas > 990000m
                        select t;
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine(tienda);
    }
 
    Console.Read();
}




La cláusula Let, también nos puede ayudar para aunar funcionalidad, para en caso de cambio tener que modificar el código en un solo lugar.


En el siguiente ejemplo haremos un filtrado de tiendas que tienen más de 10 años de vida y que han vendido de media más de 10.000 al año:



static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 10 años de vida
    /// y que tienen más de 10.000 de ventas por año
    var tiendasFiltro = from t in tiendas 
                        where          DateTime.Today.Year - t.FechaApertura.Year > 10
                        && t.Ventas / (DateTime.Today.Year - t.FechaApertura.Year) > 10000
                        select t;
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine(tienda);
    }
 
    Console.Read();
}



En esta consulta, se repetiría 2 veces la llamada a la porción de código que comprueba el nº de años:


DateTime.Today.Year - t.FechaApertura.Year > 10



Además tendríamos que tocar en 2 sitios diferentes en caso de querer cambiar esta fórmula.


Let, nos ayuda, y lo deja así de bonito:


static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 10 años de vida
    /// y que tienen más de 10.000 de ventas por año
    var tiendasFiltro = from t in tiendas 
 
                        let diferenciaAños = DateTime.Today.Year - t.FechaApertura.Year
 
                        where diferenciaAños              > 10
                        &&    t.Ventas / (diferenciaAños) > 10000
                        select t;
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine(tienda);
    }
 
    Console.Read();
}



Y el resultado, por supuesto el mismo:














Todo esto se agrava, cuando dentro de nuestras consultas, personalizamos la salida mediante el operador Select y agregamos alguno de los resultados de estas pequeñas reglas de negocio. Vamos a ver un ejemplo (una pequeña modificación del anterior):



static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 10 años de vida
    /// y que tienen más de 10.000 de ventas por año
    var tiendasFiltro = from t in tiendas
                        where          DateTime.Today.Year - t.FechaApertura.Year > 10
                        && t.Ventas / (DateTime.Today.Year - t.FechaApertura.Year) > 10000
                        select new
                        {
                            NombreTienda       = t.Nombre,
                            AñosDeExistencia   =             DateTime.Today.Year - t.FechaApertura.Year,
                            VentasMediasPorAño = t.Ventas / (DateTime.Today.Year - t.FechaApertura.Year)
                        };
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine($"Nombre:{tienda.NombreTienda} - Años de Existencia: {tienda.AñosDeExistencia} -  Ventas Medias Por Año: {tienda.VentasMediasPorAño.ToString("N2")}");
    }
 
    Console.Read();
}



Código repetido a mansalva, ejecuciones redundantes por todos lados.


Vamos a arreglarlo:


static void Main(string[] args)
{
    var tiendas = Tienda.GenerarTiendas();
 
    /// Tiendas que tienen más de 10 años de vida
    /// y que tienen más de 10.000 de ventas por año
    var tiendasFiltro = from t in tiendas
 
                        let diferenciaAños = DateTime.Today.Year - t.FechaApertura.Year
 
                        where          diferenciaAños > 10
                        && t.Ventas / (diferenciaAños) > 10000
 
                        select new
                        {
                            NombreTienda       = t.Nombre,
                            AñosDeExistencia   =             diferenciaAños,
                            VentasMediasPorAño = t.Ventas / (diferenciaAños)
                        };
 
    foreach(var tienda in tiendasFiltro)
    {
        Console.WriteLine($"Nombre:{tienda.NombreTienda} - Años de Existencia: {tienda.AñosDeExistencia} -  Ventas Medias Por Año: {tienda.VentasMediasPorAño.ToString("N2")}");
    }
 
    Console.Read();
}