Tabla de contenidos:
Select
El operador Select nos permite proyectar cada elemento de una secuencia a una nueva enumeración.
Código necesario para los ejemplos:
Una pequeña clase representando una persona con una lista de gadgets:
public class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
public List<string> Gadgets { get; set; }
public Persona(string nombre, int edad, List<string> gadgets)
{
Nombre = nombre;
Edad = edad;
Gadgets = gadgets;
}
}
Y una lista de dichas personas:
List<Persona> personas = new List<Persona>
{
new Persona("Jesus", 24, new List<string> {"Ipod", "HTC Diamond"}),
new Persona("Juan", 20, new List<string> {"Vaio", "Android"}),
new Persona("Alvaro", 24, new List<string> {"Pentium", "Movil Ladrillo"})
};
Select estándar:
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
)
El argumento es un delegado que acepta un elemento de la secuencia y devuelve el elemento que queramos proyectar. El operador devuelve una enumeración de aquellos elementos que hemos proyectado.
Queremos una lista solo con los nombres de las personas, para ello hemos de proyectar los nombres de cada persona en una nueva enumeración.
IEnumerable<string> nombres = personas.Select(persona => persona.Nombre);
Podemos convertir el operador Select a la sintaxis de un query expression:
IEnumerable<string> nombres = from persona in personas select persona.Nombre;
En ambos casos el resultado de imprimir la colección sería:
Jesus
Juan
Alvaro
Además podemos crear tipos anónimos proyectando solo las partes que queramos de una clase, por ejemplo, si solo queremos el nombre y la edad:
var nombreYEdad = from persona in personas
select new {persona.Nombre, persona.Edad};
Esto crearía un tipo anónimo y dentro sería algo así:
Nombre=Jesus Edad=24
Nombre=Juan Edad=20
Nombre=Alvaro Edad=25
Por otro lado, si quisieramos imprimir la lista de gadgets, hariamos algo así:
IEnumerable<List<string>> ListasGadgets = from persona in personas select persona.Gadgets;
Esa proyección devuelve una enumeración de listas, así que tendríamos que hacer dos iteraciones para mostrar los gadgets:
foreach (var lista in ListasGadgets) foreach (var gadget in lista) Console.WriteLine(gadget);
La salida como cabe de esperar es:
Ipod
HTC Diamond
Vaio
Android
Pentium
Movil Ladrillo
Podemos ver una versión mejorada de este último ejemplo usando SelectMany.
Select + índice:
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, int, TResult> selector
)
El delegado recibe el elemento en cuestión, además del índice de la posición del elemento en la colección.
Vamos a suponer que queremos crear un tipo anónimo con cada índice y nombre:
var nombres = personas.Select((persona, indice) => new {persona.Nombre, indice});
El resultado es:
Nombre=Jesus indice=0
Nombre=Juan indice=1
Nombre=Alvaro indice=2
Esta sobrecarga del operador Select no admite su forma en query expression.
Volver a la tabla de contenidos
SelectMany
El operador SelectMany es similar al operador Select a excepción de que SelectMany coge cada elemento que proyecta, lo convierte en una enumeración, y luego concatena todas las enumeraciones.
- Código necesario para los ejemplos
- SelectMany estándar
- SelectMany + índice
- SelectMany + función
- SelectMany + función + índice
Código necesario para los ejemplos:
Una clase representando una persona y una lista de gadgets:
public class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
public List<string> Gadgets { get; set; }
public Persona(string nombre, int edad, List<string> gadgets)
{
Nombre = nombre;
Edad = edad;
Gadgets = gadgets;
}
}
Y una lista de personas:
List<Persona> personas = new List<Persona>
{
new Persona("Jesus", 24, new List<string> {"Ipod", "HTC Diamond"}),
new Persona("Juan", 20, new List<string> {"Vaio", "Android"}),
new Persona("Alvaro", 24, new List<string> {"Pentium", "Movil Ladrillo"})
};
SelectMany estándar:
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector
)
El argumento que toma es un delegado el cual recibe el elemento y devuelve una enumeración con la proyección de cada elemento como una enumeración. Finalmente devuelve todo en una enumeración.
Como este operador es dificil de entender, vamos a compararlo directamente con Select:
Queremos una lista con todos los nombres de los gadgets, con Select ya hemos dicho que se haría así:
IEnumerable<List<string>> ListasGadgets = from persona in personas select persona.Gadgets;
El problema es que esto es una enumeración de listas, y para poder imprimir los resultados necesitamos 2 bucles:
foreach (var lista in ListasGadgets) foreach (var gadget in lista) Console.WriteLine(gadget);
Con SelectMany esto es mucho más simple:
IEnumerable<string> gadgets = personas.SelectMany(persona => persona.Gadgets);
Lo que hace exactamente es coger cada lista de gadget y las concatena una con otra y el resultado es una enumeración con los elementos de cada lista de gadget. Justo lo que estabamos buscando.
Puedes verlo de otra forma usando select en un query expression:
IEnumerable<string> gadgets = from persona in personas from gadget in persona.Gadgets select gadget;
Por cada persona de la lista y por cada gadget de cada persona, proyectamos el gadget.
Por supuesto es mas cómodo usar SelectMany.
SelectMany + índice:
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, int, IEnumerable<TResult>> selector
)
[csharp]
El delegado, además del elemento, toma un <i>int</i> el cual es el índice del elemento. Devuelve una enumeración con la proyección de cada elemento como una enumeración.
Queremos una lista de gadgets que además contenga el índice del elemento que pertenece (instancia de <i>Persona</i>):
[csharp]
var gadgets = personas.SelectMany((persona, index) =>
persona.Gadgets.Select(gadget => new {gadget, index}));
El resultado es:
gadget=Ipod index=0
gadget=HTC Diamond index=0
gadget=Vaio index=1
gadget=Android index=1
gadget=Pentium index=2
gadget=Movil Ladrillo index=2
Como podéis ver, “Ipod” y “HTC Diamond” pertenecen al primer elemento, así que el índice es 0.
SelectMany + función:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
)
En ésta sobrecarga, recibimos también un delegado que se va a ejecutar por cada elemento que vayamos proyectando. Dicho delegado recibe el elemento de la secuencia y el elemento que estamos proyectando.
Por ejemplo, si queremos convertir los nombres de los gadgets a mayusculas, usaríamos algo así:
IEnumerable<string> gadgets = personas.SelectMany(persona => persona.Gadgets, (persona, gadget) => gadget.ToUpper());
Como se puede ver, el segundo parámetro que le estamos pasando es una lambda que recibe dos parámetros: el objeto persona que es el elemento de la secuencia y cada elemento que estamos proyectando; luego simplemente le aplicamos el método ToUpper a cada gadget y ya está.
El resultado es:
IPOD
HTC DIAMOND
VAIO
ANDROID
PENTIUM
MOVIL LADRILLO
SelectMany + función + índice:
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, int, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
)
Esta sobrecarga es una combinación entre las dos anteriores, tiene la función que se va a aplicar a cada elemento proyectado y contiene el índice de cada elemento de la secuencia.
Vamos a combinar ambos ejemplos anteriores para crear un nuevo. Imprimir los gadgets en plan:
NUMERO - GADGET
Para ello usaremos esto:
IEnumerable<string> gadgets = personas.SelectMany((persona, index) => persona.Gadgets.Select(gadget => index + " - " + gadget), (persona, gadget) => gadget.ToUpper());
Básicamente recorremos cada elemento de la secuencia y de cada elemento proyectamos una cadena que contiene el indice y el nombre del gadget, posteriormente llamamos a ToUpper para ponerlo en mayusculas.
Algo más complicado que el resto, pero no es precisamente la sobrecarga de SelectMany más sencilla jeje.
El resultado es el que esperamos:
0 - IPOD
0 - HTC DIAMOND
1 - VAIO
1 - ANDROID
2 - PENTIUM
2 - MOVIL LADRILLO
Volver a la tabla de contenidos
Tags: LINQ

