domingo, 1 de mayo de 2016

Operadores de Ordenación


Llegamos al grupo de operadores destinados a realizar el trabajo de ordenación en nuestras colecciones. Dentro de este apartado nos encontramos con un nuevo actor de este teatro, un actor que tiene una importancia mínima pero que forma parte de todo este tinglao, es la interfaz IOrderedEnumerable<TElement> . Es una interfaz super simple, que hereda de IEnumerable<TElement>  y de IEnumerable, y que simplemente añade un método CreateOrderedEnumerable, cuya función será la de crear las ordenaciones. Es importante nombrarla ya que será el tipo de devolución que generen nuestros operadores de ordenación OrderBy, OrderByDescending, ThenBy y ThenByDescending.

Llegados a este momento del repaso de LinQ, volveremos a ver las diferencias para estos operadores entre la sintaxis de consultas y nuestras queridas Lambdas.







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



Clase Utilizada para los ejemplos


public class MiClase
{
    public string  ID         { get; set; }
    public int     Propiedad1 { get; set; }
    public decimal Propiedad2 { get; set; }
 
    public static IEnumerable<MiClase> GenerarDatos()
    {
        return new List<MiClase>()
        {
            new MiClase { ID = "1", Propiedad1 = 100, Propiedad2 = 100.99m },
            new MiClase { ID = "2", Propiedad1 =  20, Propiedad2 = 200.99m },
            new MiClase { ID = "3", Propiedad1 =  20, Propiedad2 = 300.99m },
            new MiClase { ID = "4", Propiedad1 =  40, Propiedad2 = 400.99m }
        };
    }
 
}



OrderBy

El operador OrderBy recibe una colección y la devuelve ordenada (de manera ascendente) por una clave o criterio de ordenación. Esta clave puede está compuesta por uno o por varios campos. Este operador tiene una sobrecarga en el que se le puede añadir un objeto de tipo IComparer<TEntity> para elegir el modo de comparación. Algo muy similar al uso de IEqualityComparer<TEntity>.



Vamos con sus firmas:


public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer);
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer);


El ejemplo de la primera sobrecarga es realmente sencillo:


class Program
{
    static void Main(string[] args)
    {
 
        var datos = MiClase.GenerarDatos();
 
 
        var datosOrdenados = datos.OrderBy(a => a.Propiedad1);
 
        foreach (var dato in datosOrdenados)
        {
            Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
        }
 
 
        Console.Read();
    }
}













Ninguna dificultad, indicamos el campo por el que queremos ordenar, mediante un selector Func<TSource, TKey> y poca historia más.

La segunda sobrecarga, nos permitirá añadir un parámetro más de tipo IComparer<TEntity>   , con el que podremos especificar una forma propia de ordenación.

 En este Link podemos ver más información acerca del uso y explicación sobre IComparer<TEntity>  . No vamos a entrar a ver ejemplos  de esta sobrecarga, ya que su uso es muy reducido. El modo de empleo como hemos comentado antes sería calcado a cualquier ejemplo que admita un IEqualityComparer<TEntity>.



OrderByDescending

Estamos ante un caso textualmente clavado al anterior, cambiando el orden de ordenación, descendente en este caso, como su propio nombre indica.

static void Main(string[] args)
{
 
    var datos = MiClase.GenerarDatos();
 
 
    var datosOrdenados = datos.OrderByDescending(a => a.Propiedad1);
 
    foreach (var dato in datosOrdenados)
    {
        Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
    }
 
 
    Console.Read();
}














ThenBy

El operador ThenBy, permite hacer una sub-ordenación, a una secuencia ya ordenada, ósea a un IOrderedEnumerable<TElement> . ThenBy, mantendrá la ordenación inicial, agregando una segunda catalogación.


Sus firmas son muy parecidas a las de OrderBy con la disparidad de que el primer parámetro del método extensor es de tipo  IOrderedEnumerable<TElement> , en decremento de IEnumerable<TElement> .


public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer);
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer);


Ejemplo:


static void Main(string[] args)
{
 
    var datos = MiClase.GenerarDatos();
 
 
    var datosOrdenados = datos.OrderBy(a => a.Propiedad1).ThenByDescending(b => b.ID);
 
    foreach (var dato in datosOrdenados)
    {
        Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
    }
 
 
    Console.Read();
}











Se puede decir que ThenBy, nos brinda la posibilidad de hacer ordenaciones, por más de un campo.



NOTA

No se puede realizar 2 OrderBy seguidos para ordenar por 2 campos o por n campos, porque el desenlace no sería el que esperamos, y es que el único criterio de ordenación que se mantendría, sería el de la última llamada, ya que cada una de las ejecuciones del método OrderBy, sobrescribiría la anterior.


Si repetimos el ejemplo anterior sustituyendo el ThenBy, por otro OrderBy:


 static void Main(string[] args)
{
 
    var datos = MiClase.GenerarDatos();
 
 
    var datosOrdenados = datos.OrderBy(a => a.Propiedad1).OrderBy(b => b.ID);
 
    foreach (var dato in datosOrdenados)
    {
        Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
    }
 
 
    Console.Read();
}











El resultado solo tendría en cuenta la ordenación por ID, obviando complemente la ordenación por Propiedad1 de la llamada anterior.




Ordenación con Sintaxis de Consulta

El azúcar sintáctico de LinQ, nos ofrece una ayuda,  fusionando los conceptos de OrderBy, OrderByDescending, ThenBy y ThenByDescending, dando sentido a su existencia, concibiendo una sintaxis muy parecida a la de Sql.


Así:


static void Main(string[] args)
{
 
    var datos = MiClase.GenerarDatos();
 
 
    var datosOrdenados = from dato in datos
                            orderby dato.Propiedad1, datoID descending
                            select dato;
 
    foreach (var dato in datosOrdenados)
    {
        Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
    }
 
 
    Console.Read();
}













Reverse

El operador Reverse, es otro de esos operadores simples de toda la biblioteca de métodos extensores de LinQ, su faena radica en generar una secuencia de salida de tipo IEnumerable<T> del mismo tipo que su secuencia de entrada, pero en orden inverso.

Aclarar que no realiza ninguna ordenación, ya que mantiene la secuencia inicial, pero al contrario de su orden:


Tan fácil como si tenemos 


0, 1, 2, 3, 4, 5, 6 ,7, 8, 9                   à          REVERSE              à           9, 8, 7, 6, 5, 4, 3, 2, 1


Vamos a ver su firma:


public static IEnumerable<TSource> Reverse<TSource>(this IEnumerable<TSource> source);


Más simple no puede ser, colección de entrada y genera colección de salida.


Ejemplo:


static void Main(string[] args)
{
    var datos = MiClase.GenerarDatos();
 
    PintarColeccion(datos, "Datos Sin Ordenar -------");
 
    var datosReverse = datos.Reverse();
 
    PintarColeccion(datosReverse, "Datos Sin Ordenados -------");
 
 
    Console.Read();
}
 
public static void PintarColeccion(IEnumerable<MiClase> source, string mensajeInicial = null)
{
    if ( ! string.IsNullOrWhiteSpace(mensajeInicial)) Console.WriteLine(mensajeInicial);
 
    foreach (var dato in source)
    {
        Console.WriteLine($"{nameof(dato.ID)}:{dato.ID} - {nameof(dato.Propiedad1)}:{dato.Propiedad1}");
    }
}