Operadores de LINQ: Conversion

Tabla de contenidos:

AsEnumerable

El operador AsEnumerable convierte una secuencia en una enumeración del tipo IEnumerable

Código necesario para los ejemplos:

Un objeto del tipo IQueryable como resultado de un LINQ to SQL:

IQueryable<Persona> personas = db.Persona;

NOTA: db es una instancia de un System.Data.Linq.DataContext o en otras palabras, una instancia del LINQ To SQL. Es un ejemplo imaginario para simplificar el ejemplo.

Volver al operador AsEnumerable

AsEnumerable:

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

͉ste operador puede parecer extraño, absurdo. Un operador que quiere transformar una secuencia a un IEnumerable y para ello necesita un … ¿IEnumerable?

Los operadores estándar son los de LINQ To Objects y esos son miembros de IEnumerable. La cosa es que hay distintas implementaciones como por ejemplo LINQ to SQL.

LINQ to SQL trabaja con IQueryable y éste implementa sus propios operadores.

Teniendo esto en consideración, IQueryable puede tener operadores como… Where, Select, etc. La cosa es que están implementados de otra forma puesto que trabajan con otros tipos de datos. No solo eso, puede que no implemente ciertos operadores que nosotros necesitamos usar.

¿Qué podemos hacer?

Si, lo que estás pensando. Podemos convertir nuestra secuencia IQueryable por una IEnumerable y ya poder usar esos operadores que faltan o las implementaciones de IEnumerable de los operadores.

¿Cómo? Así:

IEnumerable<Persona> ienupersonas = personas.AsEnumerable();

Con esto hemos convertido de IQueryable a IEnumerable.

Hemos podido usar ese método en el IQueryable puesto que esta interfaz implementa IEnumerable.

Volver a la tabla de contenidos

AsQueryable

El operador AsQueryable convierte una enumeración del tipo IEnumerable en una del tipo IQueryable

Código necesario para los ejemplos:

Una tabla de nuestra base de datos convertida a IEnumerable

IQueryable<Persona> personas = db.Persona;

IEnumerable<Persona> ienupersonas = personas.AsEnumerable();

Volver al operador AsQueryable

AsQueryable:

public static IQueryable<TElement> AsQueryable<TElement>(
	this IEnumerable<TElement> source
)

͉ste método es justo lo contrario de AsEnumerable. Recomendaría leer su descripción para mejor compensión.

Básicamente, si tienes un objeto del tipo IEnumerable como por ejemplo el de nuestro código adjunto, puedes necesitar convertirlo a IQueryable.

Por ejemplo, tenemos un IQueryable proveniente de una tabla de nuestra base de datos. La convertimos a IEnumerable como ya se explicó. Hacemos el trabajo que tengamos que hacer y luego la volvemos a convertir a IQueryable.

Para hacer esto último, hacemos un:

IQueryable<Persona> iquerypersonas = ienupersonas.AsQueryable();

Podríamos haber reusado el objeto personas pero era para dar otro ejemplo.

Volver a la tabla de contenidos

Cast

El operador Cast puede convertir un array estandar a un objeto que implementa IEnumerable.

Código necesario para los ejemplos:

Un array de enteros:

int[] arrayNum = {1, 2, 3, 4, 5};

Volver al operador Cast

Cast:

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

El operador Cast se usa con la secuencia que queremos convertir a un tio. Devolverá dicha secuencia convertida a una enumeración de dicho tipo.

Por ejemplo, queremos convertir nuestro array de enteros a una enumeración de enteros:

IEnumerable<int> enuNum = arrayNum.Cast<int>();

El resultado es:

1
2
3
4
5

Ahora la pregunta es: ¿Para qué queremos convertir el array a un IEnumerable? Hay una razón sencilla:

Un array normal y corriente no implementa IEnumerable lo que conlleva a no poder usar ningún operador de LINQ. Así que si convertimos nuestro array en un IEnumerable pues podremos aplicar algún que otro operador, por ejemplo:

IEnumerable<int> enuNum = arrayNum.Cast<int>().Select(i => i*i);

Ahora gracias a Cast hemos podido aplicarle un Select.

El resultado de esto último sería:

1
4
9
16
25

Volver a la tabla de contenidos

ToArray

El operador ToArray convierte una enumeración del tipo IEnumerable en un array.

Código necesario para los ejemplos:

Una clase persona:

public class Persona
{
	public string Nombre { get; set;}
	public int Edad { get; set;}
	public string Ciudad { get; set; }

	public Persona(string nombre, int edad, string ciudad)
	{
		Nombre = nombre;
		Edad = edad;
		Ciudad = ciudad;
	}
}

Una lista de personas:

List<Persona> personas = new List<Persona>
					 {
						 new Persona("Rodriguez, Jesús", 24, "Cádiz"),
						 new Persona("Bautista, Jesús", 15, "Alicante"),
						 new Persona("Perez, Juan", 15, "Cádiz"),
						 new Persona("García, Javier", 24, "Málaga"),
						 new Persona("Toledo, María", 37, "Málaga")
					 };

Volver al operador ToArray

ToArray:

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

͉ste operador toma como parámetro un objeto IEnumerable de un tipo y devuelve un array de dicho tipo.

Por ejemplo, tenemos una listra de personas, queremos crear un array con todos los nombres de las personas.

Lo haré en dos pasos para verlo mejor

IEnumerable<string> nombres = personas.Select(p => p.Nombre);
string[] arrayNombres = nombres.ToArray();

Como ves, proyecto solo los nombres en una enumeración de string y luego convierto dicha enumeración en un array de string.

Se puede reducir a un paso también:

string[] nombres = personas.Select(p => p.Nombre).ToArray();

En ambos casos, se imprimirían los nombres:

Rodriguez, Jesús
Bautista, Jesús
Perez, Juan
García, Javier
Toledo, María

Volver a la tabla de contenidos

ToDictionary

El operador ToDictionary convierte una enumeración del tipo IEnumerable en un Dictionary

Código necesario para los ejemplos:

Una clase persona:

public class Persona
{
	public string Nombre { get; set;}
	public string Edad { get; set;}
	public string Ciudad { get; set; }

	public Persona(string nombre, string edad, string ciudad)
	{
		Nombre = nombre;
		Edad = edad;
		Ciudad = ciudad;
	}
}

Una lista de personas:

List<Persona> personas = new List<Persona>
		 {
			 new Persona("Rodriguez, Jesús", "24", "Cádiz"),
			 new Persona("Bautista, Jesús", "15", "Alicante"),
			 new Persona("Perez, Juan", "16", "Cádiz"),
			 new Persona("García, Javier", "26", "Málaga"),
			 new Persona("Toledo, María", "37", "Málaga")
		 };

Un comparador personalizado:

public class StringANumero : IEqualityComparer<string>
{
	public bool Equals(string x, string y)
	{
		return (Int32.Parse(x) == Int32.Parse(y));
	}

	public int GetHashCode(string obj)
	{
		return Int32.Parse(obj).ToString().GetHashCode();
	}
}

Volver al operador ToDictionary

ToDictionary estándar:

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

ToDictionary recibe un delegado que usaremos para seleccionar la clave. Devuelve un diccionario cuyas claves son las que hemos seleccionado e irán con su valor correspondido. A diferencia de ToLookup en un diccionario las claves van asociadas a un solo valor. Las claves no se pueden repetir.

Por ejemplo, queremos transformar nuestra lista de personas a un diccionario donde la clave sea el nombre:

Dictionary<string, Persona> dictNombres = personas.ToDictionary(p => p.Nombre);

Y tal como pasa con ILookup podemos hacer un:

Persona jesus = dictNombres["Rodriguez, Jesús"];

Volver al operador ToDictionary

ToDictionary + comparador personalizado:

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

Con esta sobrecarga podemos crear un diccionario a partir de una clave, pero ahora usaremos un comparador personalizado para comparar las claves.

Vamos a verlo:

Dictionary<string, Persona> dictEdad = personas.ToDictionary(p => p.Edad, new StringANumero());

Nada nuevo, usamos nuestro comparador. Ahora bien, quiero recoger del diccionario la persona de 24 años, o sea, Jesús:

Persona jesus = dictEdad["000024"];

Ea, ahi está… ¿Pero por qué funciona?

Como ya vimos en ToLookup, cuando le pedimos al diccionario que nos devuelva el valor asociado a una clave (en este caso “000024″) lo que hace es buscar dicha clave en el diccionario comparando la clave que le pasamos con cada clave del diccionario. Al usar un comparador personalizado, esas comparaciones se hacen a través de nuestro comparador personalizado. Entonces, el método Parse del de la clase Int32 lo que hace es convertir nuestra cadena “000024″ a un Int32 para ello quita los ceros de la izquierda y quedaría simplemente como 24, claro, cuando llega a la entrada de Jesús ve que su edad es 24 también y la da por válida.

Volver al operador ToDictionary

ToDictionary + proyector elemento:

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

Ahora podemos definir cual será la clave y como serán los elementos gracias al delegado que nos permite proyectar lo que necesitemos de cada elemento.

Vamos a crear un diccionario con el nombre como clave, pero solo nos vamos a quedar con la ciudad, nada más:

Dictionary<string, string> dictNombre = personas.ToDictionary(p => p.Nombre, p => p.Ciudad);

Si imprimieramos el diccionario, conseguiríamos algo así:

[Rodriguez, Jesús, Cádiz]
[Bautista, Jesús, Alicante]
[Perez, Juan, Cádiz]
[García, Javier, Málaga]
[Toledo, María, Málaga]

Volver al operador ToDictionary

ToDictionary + proyector elemento + comparador personalizado:

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

Además de la clave y el proyector para los elementos, podemos usar un comparador personalizado para comparar las claves.

Vamos a mejorar el ejemplo anterior creando un diccionario con la edad como clave pero solo necesitamos el nombre de la persona y nada más:

Dictionary<string, string> dictEdad = personas.ToDictionary(p => p.Edad, p => p.Nombre, new StringANumero());

Ahora por cada clave (edad) solo habrá una cadena con el nombre y no todo el objeto Persona entero. Y cada vez que le pedimos al diccionario que nos devuelva un nombre, comprobará cada edad con nuestro comparador personalizado.

Volver a la tabla de contenidos

ToList

El operador ToList convierte una enumeración del tipo IEnumerable en un objeto del tipo List.

Código necesario para los ejemplos:

Una clase persona:

public class Persona
{
	public string Nombre { get; set;}
	public int Edad { get; set;}
	public string Ciudad { get; set; }

	public Persona(string nombre, int edad, string ciudad)
	{
		Nombre = nombre;
		Edad = edad;
		Ciudad = ciudad;
	}
}

Una lista de personas:

List<Persona> personas = new List<Persona>
					 {
						 new Persona("Rodriguez, Jesús", 24, "Cádiz"),
						 new Persona("Bautista, Jesús", 15, "Alicante"),
						 new Persona("Perez, Juan", 15, "Cádiz"),
						 new Persona("García, Javier", 24, "Málaga"),
						 new Persona("Toledo, María", 37, "Málaga")
					 };

Volver al operador ToList

ToList:

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

Recibe como parámetro el objeto tipo IEnumerable a convertir. Devuelve un objeto List de dicho tipo.

Por ejemplo, creamos una enumeración proyectando solo los nombres de las personas, luego lo ordenamos y por último lo convertimos a una lista:

IEnumerable<string> orderYSelect = personas.OrderBy(p => p.Nombre).Select(p => p.Nombre);
List<string> nombresOrdenados = orderYSelect.ToList();

O lo que es lo mismo:

List<string> nombresOrdenados = personas.OrderBy(p => p.Nombre).Select(p => p.Nombre).ToList();

El resultado de imprimir esto sería:

Bautista, Jesús
García, Javier
Perez, Juan
Rodriguez, Jesús
Toledo, María

Volver a la tabla de contenidos

Consideraciones finales

Algo importante que añadir:

Como ya dije, la gran mayoría de operadores de LINQ son de ejecución diferida, esto quiere decir que cuando lo aplicas a un objeto y obtienes un IEnumerable realmente no estás ejecutando nada en ningún elemento.

Visto de otra forma, si tienes un:

IEnumerable<string> nombres = personas.Select(p => p.Nombre);

Realmente IEnumerable no contiene los nombres de cada persona, lo que contiene son las instrucciones para conseguir los nombres. Cuando iteras sobre ese objeto es cuando realmente estás procesando cada elemento, pero no antes. Eso quiere decir que puedes ir aplicandole operadores de forma posterior sin coste alguno (en rendimiento) puesto que no estás ejecutando nada, simplemente estás modificando las instrucciones que ya habían.

Ahora bien, los operadores del tipo ToXXX hacen que se ejecute directamente dichas instrucciones y cree una colección con todos los elementos procesados.

Eso quiere decir que si haces un:

List<string> nombres = personas.Select(p => p.Nombre).ToList();

Estarás haciendo que se procese cada persona para proyectarle el nombre y meterlo en una lista.

En resumen, los métodos ToXXX son mucho más lentos que los de ejecución diferida, así que hay que intentar usar estos últimos primero puesto que así ganaremos mucho en rendimiento.

Volver a la tabla de contenidos

Tags:

5 comentarios

  1. Hola

    No entiendo esto “Cuando iteras sobre ese objeto es cuando realmente..”
    Que significa iters? Es cuando le asigno la variable a una grilla:
    DataGridView.DataSource = xListaClientes;

    Eso es iterar?
    Graciasss

  2. Hola, iterar es ir elemento a elemento, iterar sobre una lista por ejemplo sería ir elemento por elemento haciendo algo.

  3. Tengo una pregunta:
    Estoy desarrollando en Visual Studio 2010 para Windows Phone 7 y cuando quiero converetir un IQueryable a un List me sale un error que dice InvalidCastException. Es decir, no puedo convertir y no entiendo por qué!
    Agradecería tu ayuda.

  4. Tendría que ver algo de código para probarlo yo al menos :P (un pastebin o algo)

Dejar un comentario