domingo, 8 de mayo de 2016

Operadores de Conversión






Como su propio nombre indica, los operadores de conversión realizan transformaciones de datos de un tipo de colecciones a otras.


En este caso tenemos un grupo de operadores de conversión que son de ejecución instantánea, compuesta por los operadores ToList, ToArray, ToLookUp y ToDictionary, y otro grupo de carga perezosa o diferida, compuesta por OffType y Cast. De este último tipo también sería el operador AsEnumerable, pero en éste no pondremos mucho énfasis, ya que es un operador que se utiliza más en LinqToSql o LinqToEntities, y este curso es de LinqToObjects.









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



Vamos con una de las entradas más sencillas de todas las que hemos visto hasta ahora.




AsEnumerable

El operador AsEnumerable, recibe una secuencia de tipo IEnumerable<T>, para devolver otra de IEnumerable<T>. Esto puede parecer una tontería si lo miramos de forma literal, ya que podríamos pensar que toma un objeto de un tipo para devolver el mismo objeto, pero no es exactamente así, ya que el tipo extensor inicial de su parámetro, no tiene por qué ser de tipo IEnumerable<T> estrictamente, puede ser de cualquier tipo que implemente esta interfaz.


Su firma:


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


Como hemos comentado en la introducción, en la práctica, este operador suele utilizarse solo entre conversiones de tipo IQueriable<T> a IEnumerable<T>, para transformaciones de operaciones entre LinqToSql, LinqToEnties a LinqToObjects.




ToList

Este operador transforma cualquier secuencia de tipo IEnumerable<T> en List<T>. Es uno de los mñas utilizados en todo el ecosistema de LinQ, ya que  al realizar esta conversión, la lista de resultado, tendrá disponible los métodos AddAddRange, Insert, Remove, RemoveAt, etc, disponibles con la interfaz IList<T> e ICollection<T>, que no se encuentran vacante en la interfaz IEnumerable<T>.

ToList, es de los operadores más utilizados para romper la carga perezosa o diferida y para ejecutar las consultas de forma inmediata.


Esta es su firma:


public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source);


















IEnumerable<T> no tiene los métodos (anteriormente nombrados, Add, AddRange, Insert, etc) disponibles.




















ToArray

Mismo comportamiento que para el caso de ToList, pero con una amplitud de uso mucho más reducida, normalmente utlizada para acciones de compatibilidad.


Firma:


public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source);


Ejemplo:


string[] lineas = System.IO.File.ReadAllLines("C:\\file.txt");
 
lineas          = lineas.Where(a => ! string.IsNullOrEmpty(a)).ToArray();


Consumimos el operador ToArray, para reutilizar la variable de tipo string[].



ToDictionary

El operador ToDictionary, convierte una colección de entrada de tipo IEnumerable<T>, en un Diccionario Genérico de tipo Clave/Valor.

ToDictionary, es un poco más complejo que los demás, y posee 4 sobrecargas. Vamos a destriparlo un poquito y a desvanecer esa dificultad.


Estas son sus 4 sobrecargas:


public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer);

public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector);
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer);



La primera sobrecarga, aparte del parámetro extensor, toma un parámetro de tipo Func<TSource, TKey>, ósea un selector igual que el del operador Select, con el que valga la redundancia, seleccionaremos el campo u objeto que deseamos que sea la clave de nuestro Dictionary.


Utilizaremos la siguiente clase para los ejemplos:

Code:
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 }
        };
    }
    
    public override string ToString() => $"[({nameof(ID)}:{ID} - {nameof(Propiedad1)}:{Propiedad1} - {nameof(Propiedad2)}:{Propiedad2}]";

}



Ejemplo de la primera sobrecarga:


static void Main(string[] args)
{
    var datos = MiClase.GenerarDatos();
 
    Dictionary<string, MiClase> diccionario = datos.ToDictionary(a => a.ID);
 
    foreach(var item in diccionario)
    {
        Console.WriteLine($"KEY --> {item.Key} *** VALUE -> {item.Value.ToString()}");
    }
 
    Console.Read();
}



Ejemplo muy sencillo en el que mediante una lambda, indicamos el campo de nuestra clase MiClase, que hemos elegido para que sea la clave (Key) de nuestro diccionario.













La 3ª sobrecarga (la 2ª y la 4ª las dejaremos para el final, ya que están muy vinculadas), añade un selector más   , en este caso Func<TSource, TKey> para la propiedad Value de cada uno de los objetos de nuestro diccionario.


static void Main(string[] args)
{
    var datos = MiClase.GenerarDatos();
 
    Dictionary<string, decimal> diccionario = datos.ToDictionary(a => a.ID, b => b.Propiedad2);
 
    foreach(var item in diccionario)
    {
        Console.WriteLine($"KEY --> {item.Key} *** VALUE -> {item.Value.ToString()}");
    }
 
    Console.Read();
}



El ejemplo es similar al anterior, con la disconformidad de que el valor asignado a la propiedad Value de cada uno de los elementos de nuestro diccionario, no es una referencia al objeto de la colección inicial, es un objeto de tipo decimal, que se corresponde con el tipo de datos de la Propiedad2 de nuestra clase MiClase. Por este motivo la firma del diccionario que recibirá el resultado del operador ToDictionary también ha cambiado y ahora es Dictionary<string, decimal>.














Para explicar los dos sobrecargas de ToDictionary que nos quedan en el tintero (la 2ª y la 4ª), vamos a modificar un poco nuestra clase MiClase y vamos a ejecutar unos de los ejemplos anteriores


Así queda ahora la clase MiClase:


 public static IEnumerable<MiClase> GenerarDatos()
{
    return new List<MiClase>()
    {
        new MiClase { ID = "uno", Propiedad1 = 100, Propiedad2 = 100.99m },
        new MiClase { ID = "uno", Propiedad1 =  20, Propiedad2 = 200.99m },
        new MiClase { ID = "3"  , Propiedad1 =  20, Propiedad2 = 300.99m },
        new MiClase { ID = "4"  , Propiedad1 =  40, Propiedad2 = 400.99m }
    };
}


Hemos cambiado valores de propiedades para que concuerden y entren en conflicto.


Si lanzamos el ejemplo anterior, nos encontraríamos con la siguiente Excepcion:






































Se estaría elevando una Excepción de tipo System.ArgumentException, a la hora de ejecutar nuestro operador ToDictionary. Esto es debido a que la propiedad que hemos seleccionado como Clave (Key) tiene elementos repetidos, y esto no está permitido dentro de un diccionario.


Para gestionar esta posible colisión, se nos presenta la posibilidad de añadir otro parámetro más de tipo IEqualityComparer<T>  a cada una de las sobrecargas que ya hemos visto, y que formarían la 2ª y 4ª sobrecarga. Estos ejemplos tendrían la misma fórmula que los ya vistos con anterioridad durante el curso. Para ello ver este link.




ToLookUp

El operador ToLookUp, es un operador que se podría haber visto perfectamente en la parte dedicada a GrupBy (Agrupaciones). Este operador es utilizado para facilitar de forma muy eficiente las creación de secuencias con relaciones de uno a muchos (1 – n). Su uso es muy similar al operador ToDictionary, con la salvedad de la multiplicidad de sus datos:

ToDictionary à Por cada KEY, hay un VALUE relacionado.
ToLookUp     à Por cada KEY, hay N VALUES relacionados.

El operador ToLookUp, trabajará agrupando los elementos según la clave proporcionada.


Para no dejar de lado la comparación con ToDictionary, diremos que ToLookUp, también posee 4 sobrecargas, y que cada una de sus firmas, son muy similares, eso sí, llevadas a cada uno de sus terrenos.


public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector);
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer);
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector);
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer);


Para hacer los ejemplos un poco más entendibles, completamos nuestra clase:


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 },
            new MiClase { ID = "5", Propiedad1 =  20, Propiedad2 = 500.99m },
            new MiClase { ID = "6", Propiedad1 =  20, Propiedad2 = 600.99m },
            new MiClase { ID = "7", Propiedad1 =  40, Propiedad2 = 700.99m }
        };
    }
 
    public override string ToString() => $"[({nameof(ID)}:{ID} - {nameof(Propiedad1)}:{Propiedad1} - {nameof(Propiedad2)}:{Propiedad2}]";
 
}


La primera sobrecarga, aparte del parámetro del método extensor, recibe un selector simple Func<TSource, TKey> . Con este selector, al igual que en los ejemplos anteriores, seleccionaremos el campo por el que realizaremos la agrupación.


static void Main(string[] args)
{
    var datos = MiClase.GenerarDatos();
 
    var grupos = datos.ToLookup(a => a.Propiedad1);
 
    foreach(var grupo in grupos)
    {
        Console.WriteLine($"Grupo-KEY --> {grupo.Key}");
 
        foreach (var itemGrupo in grupo)
        {
            Console.WriteLine($"   ItemGrupo -> {itemGrupo.ToString()}");
        }
    }
 
    Console.Read();
}


En el ejemplo, hacemos una agrupación por la propiedad Propiedad1 e imprimimos en la consola, cada uno de sus valores.
















Como con el operador anterior, pasaremos directamente a la 3ª sobrecarga, ya que las restantes (2ª y 4ª), tienen casos muy semejantes.


La tercera sobrecarga, aparte del parámetro extensor y del selector de la clave, admite un tercer parámetro de tipo selector Func<TSource, TKey> también  para seleccionar el campo(s), que tendrá el grupo de datos agrupados.     


static void Main(string[] args)
{
    var datos = MiClase.GenerarDatos();
 
    var grupos = datos.ToLookup(a => a.Propiedad1, b => b.Propiedad2);
 
    foreach(var grupo in grupos)
    {
        Console.WriteLine($"Grupo-KEY --> {grupo.Key}");
 
        foreach (var itemGrupo in grupo)
        {
            Console.WriteLine($"   ItemGrupo -> {itemGrupo.ToString()}");
        }
    }
 
    Console.Read();
}


Mismo ejemplo, pero en vez de guardar en el grupo el objeto de tipo MiClase completo, guardamos solo el campo Propiedad2.















Para no cambiar demasiado, las sobrecargas 2ª y 4ª, serán iguales a la 1ª y 3ª, pero añadiendo un parámetro de tipo IEqualityComparer<T>. Misma explicación y mismo uso que para ToDictionary.




Cast

El operador Cast, es utilizado generalmente para convertir secuencias no-genéricas (colecciones de datos de tipo object incluídas en el espacio de nombres System. Collections), en IEnumerables<T>. Este operador es un operador muy estricto, y en caso de que alguno de los elementos de la secuencia no pueda ser convertido, lanzará un InvalidCastException.


Solo tiene una firma:


public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source);


Ejemplo:


static void Main(string[] args)
{
    ArrayList datos = new ArrayList { 1, 2, 3, 4, 5, 6 };
 
    IList<int> datosTransformados = datos.Cast<int>().ToList();
 
    Console.Read();
}


Un ejemplo que elevaría una excepción:


static void Main(string[] args)
{
    try
    {
        ArrayList datos = new ArrayList { 1, 2, 3, 4, "cinco", 6 };
 
        IList<int> datosTransformados = datos.Cast<int>().ToList();
 
        Console.Read();
    }
    catch (InvalidCastException ex)
    {
        Console.Write($"La colección tiene elementos que no se puede transformar al tipo int");
        Console.Read();
    }
}




OfType

Tiene las mismas atribuciones que el atributo Cast, con la disconformidad de que en caso de que haya algún elemento que no se pueda convertir, este se obviará de la secuencia final de resultado.


Esta es la firma de su método extensor:


public static IEnumerable<TResult> OfType<TResult>(this IEnumerable source);

Vamos a reutilizar el ejemplo anterior, que elevaba la excepción, para ver el resultado con el operador OfType:


static void Main(string[] args)
{
    try
    {
        ArrayList datos = new ArrayList { 1, 2, 3, 4, "cinco", 6 };
 
        IList<int> datosTransformados = datos.Cast<int>().ToList();
 
        Console.Read();
    }
    catch (InvalidCastException ex)
    {
        Console.Write($"La colección tiene elementos que no se puede transformar al tipo int");
        Console.Read();
    }
}














Cambiando de Cast a OfType, no generamos excepción alguna, pero perdemos en el camino el valor “cinco”, del ArrayList, ya que no puede transformarse al tipo de la secuencia de resultado.