operadores de linq
TRANSCRIPT
Operadores de LINQ
Jesús Rodríguez Rodríguez
2 Operadores de LINQ
Primera Edición, Mayo de 2010 http://www.foxandxss.net Libro publicado bajo la licencia Creative Commons
3 Operadores de LINQ
OPERADORES DE LINQ 1
1. FILTERING (FILTRADO) 6
1.1 OFTYPE 6
1.2 WHERE 7
2. PROJECTION (PROYECCIÓN) 9
2.1 SELECT 9
3. PARTITIONING (PARTICIONADO) 17
3.1 SKIP 17
3.2 SKIPWHILE 18
3.4 TAKEWHILE 21
4. JOIN (UNIÓN) 23
4.1 GROUPJOIN 23
5. CONCATENATION (CONCATENACIÓN) 32
5.1 CONCAT 32
6. ORDERING (ORDENACIÓN) 33
6.1 ORDERBY 33
6.2 ORDERBYDESCENDING 35
6.3 REVERSE 38
6.4 THENBY 39
6.5 THENBYDESCENDING 42
7. GROUPING (AGRUPACIÓN) 45
4 Operadores de LINQ
7.1 GROUPBY 45
7.2 TOLOOKUP 55
8. SET (CONJUNTO) 60
8.1 DISTINCT 60
8. SET (CONJUNTO) 62
8.3 INTERSECT 64
8.4 UNION 67
9. CONVERSION (CONVERSIÓN) 70
9.1 ASENUMERABLE 70
9.2 ASQUERYABLE 71
9.3 CAST 72
9.4 TOARRAY 73
9.5 TODICTIONARY 74
9.6 TOLIST 78
10. EQUALITY (IGUALDAD) 80
10.1 SEQUENCEEQUAL 80
11. ELEMENT (ELEMENTO) 82
11.1 ELEMENTAT 82
11.2 ELEMENTATORDEFAULT 82
11.3 FIRST 83
11.4 FIRSTORDEFAULT 84
11.5 LAST 85
11.6 LASTORDEFAULT 87
11.7 SINGLE 88
11.8 SINGLEORDEFAULT 89
5 Operadores de LINQ
12. GENERATION (GENERACIÓN) 91
12.1 DEFAULTIFEMPTY 91
12.2 EMPTY 92
12.3 RANGE 93
12.4 REPEAT 94
13. QUANTIFIER (CUANTIFICADORES) 95
13.1 ALL 95
13.2 ANY 96
13.3 CONTAINS 97
14. AGGREGATION (AGREGACIÓN) 100
14.1 AGGREGATE 100
14.2 AVERAGE 102
14.3 COUNT 104
14.4 LONGCOUNT 105
14.5 MAX 106
14.6 MIN 108
14.7 SUM 109
6 1. Filtering (Filtrado)
1. Filtering (Filtrado)
1.1 OfType
El operador OfType nos permite filtrar una secuencia dado un tipo.
1.1.1 Código necesario para los ejemplos
Lista de objetos al azar:
object[] objetos = { "LINQ", 1, 23.4F, "Perro", new object(), "Casa" };
1.1.2 OfType
public static IEnumerable<TResult> OfType<TResult>( this IEnumerable source )
Le especificamos el tipo que queremos filtrar y nos devuelve una enumeración de dicho tipo.
Por ejemplo, si queremos solo los elementos del tipo string usamos el operador OfType para filtrar esa secuencia:
IEnumerable<string> cadenas = objetos.OfType<string>();
Si imprimimos el resultado, la salida sería:
LINQ
Perro
Casa
7 Operadores de LINQ
1.2 Where
El operador Where nos permite filtrar una secuencia, pero a diferencia de OfType, el operador Where es mucho más flexible puesto que admite como parámetro un delegado.
1.2.1 Código necesario para los ejemplos
Lista de títulos (imaginarios) de libros:
var libros = new List<string> { "Programando en C#", "LINQ para torpes", "WPF para todos", "Empezando con LINQ", "LINQ Avanzado" };
1.2.2 Where estándar
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
En este caso Where acepta un delegado el cual recibe como parámetro un elemento de la secuencia y nos devuelve un bool que nos indica si ese elemento se filtra o no. El operador devuelve una enumeración con los elementos filtrados.
Queremos una nueva lista de libros donde sólo estén los que sean de LINQ, para ello usamos Where.
IEnumerable<string> librosLINQ = libros.Where( libro => libro.Contains("LINQ"));
Además, el operador Where se puede convertir a la sintaxis de un query expression:
IEnumerable<string> librosLINQ = from libro in libros where libro.Contains("LINQ") select libro;
8 1. Filtering (Filtrado)
En ambos casos, el resultado de imprimirlo sería:
LINQ para torpes
Empezando con LINQ
LINQ Avanzado
1.2.3 Where + índice
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate )
En este caso, el delegado acepta también un int el cual representa la posición de cada elemento en la secuencia que estamos filtrando.
Así que si por ejemplo queremos aquellos libros de LINQ que estén dentro de las 3 primeras posiciones de la secuencia, podemos usar el siguiente fragmento:
IEnumerable<string> librosLINQ = libros.Where((libro, index) => libro.Contains("LINQ") && index < 3);
Esto imprimiría:
LINQ para Torpes
La versión de Where que acepta también el índice, no puede ser convertida a la sintaxis de los query expressions.
9 Operadores de LINQ
2. Projection (Proyección)
2.1 Select
El operador Select nos permite proyectar cada elemento de una secuencia a una nueva enumeración.
2.1.1 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"} ) };
10 2. Projection (Proyección)
2.1.2 Select estándar
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<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 secuencia sería:
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
11 Operadores de LINQ
Por otro lado, si quisiéramos imprimir la lista de gadgets, haríamos algo así:
<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.
2.1.3 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 secuencia.
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 });
12 2. Projection (Proyección)
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.
2.2 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.
2.2.1 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 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"} ),
13 Operadores de LINQ
new Persona("Alvaro", 24, new List<string> {"Pentium",
"Movil Ladrillo"} ) };
2.2.2 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 difícil 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í:
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 gadgets y las concatena una con otra y el resultado es una enumeración con los elementos de cada lista de gadgets. Justo lo que estábamos 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;
14 2. Projection (Proyección)
Por cada persona de la lista y por cada gadget de cada persona, proyectamos el gadget.
Por supuesto es más cómodo usar SelectMany.
2.2.3 SelectMany + Índice
public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TResult>> selector )
El delegado, además del elemento, toma un int 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 Persona):
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.
2.2.4 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 )
15 Operadores de LINQ
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 mayúsculas, 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
2.2.5 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 uno nuevo.
16 2. Projection (Proyección)
Queremos 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 índice y el nombre del gadget, posteriormente llamamos a ToUpper para ponerlo en mayúsculas.
Algo más complicado que el resto, pero no es precisamente la sobrecarga de SelectMany más sencilla.
El resultado es el que esperamos:
0 - IPOD
0 - HTC DIAMOND
1 - VAIO
1 - ANDROID
2 - PENTIUM
2 - MOVIL LADRILLO
17 Operadores de LINQ
3. Partitioning (Particionado)
3.1 Skip
El operador Skip nos sirve para ignorar los X primeros elementos de una secuencia.
3.1.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Alvaro", "Manolo", "Juan", "Paco", "Rosi", "Maria", "Amanda", "Julia" };
3.1.2 Skip
public static IEnumerable<TSource> Skip<TSource>( this IEnumerable<TSource> source, int count )
Skip toma como argumento un número que será la cantidad de elementos de la secuencia a ignorar. Devuelve una enumeración con los elementos restantes.
Teniendo la lista de nombre, queremos saltarnos los 5 primeros para así quedarnos con los nombres de mujer:
IEnumerable<string> mujeres = nombres.Skip(5);
18 3. Partitioning (Particionado)
El resultado sería:
Rosi
Maria
Amanda
Julia
Si como argumento le pasamos un cero o un número negativo, no ignorará nada.
3.2 SkipWhile
El operador SkipWhile como su homónimo Skip sirve para ignorar elementos. Pero dada una condición y no un número.
3.2.1 Código necesario para los ejemplos
Una lista de calificaciones:
List<double> notas = new List<double> { 0, 1.2, 2, 3, 4.5, 4.9, 6, 6, 7.6, 9, 10 };
3.2.2 SkipWhile estándar
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
SkipWhile toma un delegado el cual recibe el elemento de la secuencia y devuelve un bool. El operador devuelve una enumeración con los elementos no ignorados.
Cada elemento irá pasando por el delegado y devolverá true o false dependiendo de una condición. Mientras vaya devolviendo true, esos elementos se irán ignorando. Lo importante de esto es que cuando uno de los elementos devuelva false, se devuelve este elemento y los restantes de la secuencia sin comprobarlos.
Este operador se suele usar con los operadores de ordenación, pero puesto que prefiero mostrar ejemplos que involucren la menor cantidad
19 Operadores de LINQ
de operadores posibles, vamos a probar con una lista ya ordenada de antemano.
Imaginemos que nuestra condición es ir ignorando toda calificación considerada suspendida hasta que encontremos una aprobada:
IEnumerable<double> aprobados = notas.SkipWhile(nota => nota < 5);
Aquí la condición es sencilla, ignorar toda nota que sea menor a 5. Como veis es importante que la secuencia a tratar esté ordenada puesto que cuando encuentre una nota mayor a 5, dejará de hacer comprobaciones y meterá en la enumeración todas las notas restantes estén aprobadas o no.
El resultado es:
6
6
7.6
9
10
3.2.3 SkipWhile + índice
public static IEnumerable<TSource> SkipWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate )
En esta sobrecarga, el delegado además del elemento, recibe también el índice de dicho elemento en la secuencia. Lo cual nos da más posibilidades a la hora de ignorar elementos.
Como antes, es difícil mostrar el uso de esta sobrecarga si no es en conjunción con otros operadores, pero como la sencillez es lo más importante a la hora de aprender, vamos con este ejemplo:
IEnumerable<double> variasNotas = notas.SkipWhile((nota, index) => nota < index + 0.5);
Estamos ignorando todas las notas hasta que una de ellas sea más alta que su índice + 0.5.
20 3. Partitioning (Particionado)
Así que el resultado es:
4,5
4,9
6
6
7,6
9
10
3.3 Take
El operador Take es justo lo contrario al operador Skip Tomará aquellos X primeros elementos de una secuencia e ignorará el resto.
3.3.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Alvaro", "Manolo", "Juan", "Paco", "Rosi", "Maria", "Amanda", "Julia" };
3.3.2 Take
public static IEnumerable<TSource> Take<TSource>( this IEnumerable<TSource> source, int count )
Take toma como argumento un número que será la cantidad de elementos de la secuencia a devolver. Devuelve una enumeración con dichos elementos ignorando el resto.
En este caso, si necesitamos los 5 primeros nombres, haríamos algo así:
IEnumerable<string> hombres = nombres.Take(5);
21 Operadores de LINQ
Esto imprimiría:
Jesus
Alvaro
Manolo
Juan
Paco
Si le pasamos un número negativo o cero, no devolverá nada.
3.4 TakeWhile
El operador TakeWhile como su homónimo Take sirve para devolver los elementos de una secuencia pero en este caso los irá devolviendo mientras la condición devuelva true.
3.4.1 Código necesario para los ejemplos
Una lista de calificaciones:
List<double> notas = new List<double> { 0, 1.2, 2, 3, 4.5, 4.9, 6, 6, 7.6, 9, 10 };
3.4.2 TakeWhile estándar
public static IEnumerable<TSource> TakeWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
TakeWhile toma un delegado el cual recibe el elemento de la secuencia y devuelve un bool. El operador devuelve una enumeración con los elementos no ignorados.
Cada elemento irá pasando por el delegado y devolverá true o false dependiendo de una condición. Mientras devuelva true, los elementos se irán devolviendo. Lo importante de esto es que cuando uno de los elementos devuelva false, se ignorará este elemento y los restantes de la enumeración sin comprobarlos.
Este operador se suele usar con los operadores de ordenación, pero puesto que prefiero mostrar ejemplos que involucren la menor cantidad
22 3. Partitioning (Particionado)
de operadores posibles, vamos a probar con una lista ya ordenada a mano.
Vamos a coger todas las notas suspensas (para regañar a aquellos que no aprobaron :P):
IEnumerable<double> suspensos = notas.TakeWhile(nota => nota < 5);
Mientras la nota sea menor a 5, ir devolviéndola. Cuando una sea mayor, ignorar el resto de la secuencia.
El resultado es:
0
1.2
2
3
4.5
4.9
3.4.3 TakeWhile + Índice
public static IEnumerable<TSource> TakeWhile<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate )
En esta sobrecarga, el delegado además del elemento, recibe también el índice de dicho elemento en la secuencia. Lo cual nos da más posibilidades a la hora de devolver elementos.
IEnumerable<double> variasNotas = notas.TakeWhile((nota, index) => nota < index + 0.5);
Mientras la nota sea menor al valor del índice + 0.5, añadirla a la enumeración.
23 Operadores de LINQ
4. Join (Unión)
4.1 GroupJoin
El operador GroupJoin nos permite establecer una relación entre 2 secuencias y agrupar los resultados. Básicamente agrupa un elemento de la secuencia externa a una colección de elementos de la secuencia interna.
4.1.1 GroupJoin estándar
4.1.1.1 Código necesario
Una clase Provincia:
public class Provincia { public string Nombre { get; set; } public Provincia(string nombre) { Nombre = nombre; } }
Una clase Ciudad:
public class Ciudad { public string Nombre { get; set; } public Provincia Localizacion { get; set; } public Ciudad(string nombre, Provincia localizacion) { Nombre = nombre; Localizacion = localizacion; } }
Creamos varias provincias y varias ciudades:
Provincia cadiz = new Provincia("Cádiz"); Provincia malaga = new Provincia("Málaga"); Provincia madrid = new Provincia("Madrid"); Provincia barcelona = new Provincia("Barcelona"); Ciudad algeciras = new Ciudad("Algeciras", cadiz); Ciudad jerez = new Ciudad("Jerez", cadiz);
24 4. Join (Unión)
Ciudad ronda = new Ciudad("Ronda", malaga); Ciudad churriana = new Ciudad("Churriana", malaga); Ciudad alcobendas = new Ciudad("Alcobendas", madrid); Ciudad fuenlabrada = new Ciudad("Fuenlabrada", madrid);
Y las metemos en listas:
List<Provincia> listaProvincias = new List<Provincia> { cadiz, malaga, madrid, barcelona }; List<Ciudad> listaCiudades = new List<Ciudad> { algeciras, jerez, ronda, churriana, alcobendas, fuenlabrada };
4.1.1.2 Definición
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector )
El operador recibe varios parámetros:
Recibe una secuencia la cual llamaremos secuencia interna (La externa es en la que aplicamos el operador).
Recibe un delegado que usaremos para extraer la clave de la secuencia externa.
Recibe un delegado que usaremos para extraer la clave de la secuencia interna.
El último delegado lo usaremos para crear la enumeración que saldrá como resultado al combinar un elemento de la secuencia externa con una colección de elementos de la secuencia interna.
El operador devuelve la enumeración resultante.
Vamos a verlo con un ejemplo. Queremos crear una enumeración donde agruparemos las provincias con una colección de ciudades que están dentro de dicha provincia:
var query = listaProvincias.GroupJoin(listaCiudades, provincia => provincia, ciudades => ciudades.Localizacion, (provincia, ciudades) => new { provincia, ciudades });
25 Operadores de LINQ
Por pasos:
El primer parámetro es la secuencia interna, en este caso la lista de ciudades (La externa es la lista de provincias).
La clave externa será el elemento en si (de la secuencia externa).
La clave interna será la propiedad Localizacion de cada ciudad.
Luego creamos un tipo anónimo que consistirá en una provincia más una lista de ciudades.
Parece complicado pero no lo es, simplemente colocamos como clave los 2 elementos que harán la unión, en este caso un objeto Provincia con otro objeto Provincia dentro de cada ciudad. Así que simplemente cada provincia estará asociada a una lista de ciudades que la tengan como provincia.
Como alternativa, podemos usar la sintaxis de los query expression:
var query = from ciudad in listaCiudades group ciudad by ciudad.Localizacion into ciudades select new { ciudades.Key.Nombre, ciudades };
Por cada ciudad de la lista, meterlas en un grupo que tengan la Localización en común. Llamar a ese grupo ciudades. De ahí creamos un tipo anónimo con el nombre de la clave (es el elemento que hemos usado para hacer la unión, en este caso la provincia) y la lista de ciudades de dicho grupo.
Es lo mismo pero con la sintaxis alternativa que nos da algunos operadores de LINQ.
En ambos casos el resultado podría ser así:
Cádiz - (Algeciras, Jerez)
Málaga - (Ronda, Churriana)
Madrid - (Alcobendas, Fuenlabrada)
Barcelona - ()
26 4. Join (Unión)
4.1.2 GroupJoin + Comparador personalizado
4.1.2.1 Código necesario
Una lista de iniciales y otra de palabras:
List<string> listaIniciales = new List<string> { "A", "B", "C", "D" };
List<string> listaPalabras = new List<string> { "Arroz", "Antena",
"Barco", "Caballo", "Decir" };
Un comparador personalizado:
public class ComparadorIniciales : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
4.1.2.2 Definición
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el operador estándar, simplemente nos da la posibilidad de usar un comparador personalizado.
En el anterior ejemplo, habíamos simplemente comparado 2 instancias de por si iguales, ahora vamos a poner un ejemplo donde necesitaremos un comparador personalizado.
Tenemos una lista con iniciales y una lista de palabras. Queremos hacer una unión donde cada inicial estará asociada a una colección de palabras que empiecen por dicha inicial.
27 Operadores de LINQ
El código sería:
var query = listaIniciales.GroupJoin(listaPalabras, inicial => inicial, palabras => palabras, (inicial, palabras) => new { inicial, palabras }, new ComparadorIniciales());
La forma de usarlo es la misma que con el operador estándar, simplemente añadimos una instancia de nuestro comparador personalizado que hará el trabajo de comprobar si una inicial y una palabra está relacionada.
Por ser la primera vez que usamos un comparador personalizado, lo explicaré un poco.
El comparador ha de implementar IEqualityComparer<T> y dicha interfaz viene con 2 métodos.
Por cada elemento comprobará el hashcode y cuando 2 elementos tienen el mismo hashcode llamará a equals para comprobar si esos dos elementos son iguales.
Por otro lado, cabe decir que jamás de los jamases usaríamos esta sobrecarga para hacer un trabajo como este, puesto que el operador estándar es capaz de hacer una unión de este tipo o directamente usar otro operador como SelectMany. Simplemente esto es un ejemplo para que veáis como implementar un comparador personalizado y usarlo.
Finalmente el resultado es algo tipo:
A - (Arroz, Antena)
B - (Barco)
C - (Caballo)
D - (Decir)
28 4. Join (Unión)
4.2 Join
El operador Join sirve para establecer una relación entre 2 secuencias comparando sus claves.
4.2.1 Join estándar
4.2.1.1 Código necesario
Una clase Provincia:
public class Provincia { public string Nombre { get; set; } public Provincia(string nombre) { Nombre = nombre; } }
Una clase Ciudad:
public class Ciudad { public string Nombre { get; set; } public Provincia Localizacion { get; set; } public Ciudad(string nombre, Provincia localizacion) { Nombre = nombre; Localizacion = localizacion; } }
Creamos varias provincias y varias ciudades:
Provincia cadiz = new Provincia("Cádiz"); Provincia malaga = new Provincia("Málaga"); Provincia madrid = new Provincia("Madrid"); Provincia barcelona = new Provincia("Barcelona"); Ciudad algeciras = new Ciudad("Algeciras", cadiz); Ciudad jerez = new Ciudad("Jerez", cadiz); Ciudad ronda = new Ciudad("Ronda", malaga); Ciudad churriana = new Ciudad("Churriana", malaga); Ciudad alcobendas = new Ciudad("Alcobendas", madrid); Ciudad fuenlabrada = new Ciudad("Fuenlabrada", madrid);
29 Operadores de LINQ
Y las metemos en listas:
List<Provincia> listaProvincias = new List<Provincia> { cadiz, malaga, madrid, barcelona }; List<Ciudad> listaCiudades = new List<Ciudad> { algeciras, jerez, ronda, churriana, alcobendas, fuenlabrada };
4.2.1.2 Definición
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector )
El operador recibe varios parámetros:
Recibe una secuencia la cual llamaremos secuencia interna (La externa es en la que aplicamos el operador).
Recibe un delegado que usaremos para extraer la clave de la secuencia externa.
Recibe un delegado que usaremos para extraer la clave de la secuencia interna.
El último delegado lo usaremos para crear la enumeración que saldrá como resultado al combinar un elemento de la secuencia externa con un elemento de la secuencia interna.
El operador devuelve la enumeración resultante.
Por ejemplo, queremos una secuencia compuesta por una provincia y una ciudad que pertenezca a esa provincia:
var query = listaProvincias.Join(listaCiudades, provincia => provincia, ciudad => ciudad.Localizacion, (provincia, ciudad) => new { provincia, ciudad });
A diferencia del operador GroupJoin aquí se creará un elemento por cada relación. Eso quiere decir, que si tenemos 2 ciudades de Cádiz, saldrán 2 entradas en vez de 1 con varios elementos.
30 4. Join (Unión)
Esto es lo que comúnmente se llama un Inner Join que básicamente es encontrar la relación entre 2 secuencias, y cada elemento de las 2 secuencias que se relacionen irán a parar a una nueva enumeración.
Podemos representar un Inner Join usando la sintaxis de un query expression:
var query = from provincia in listaProvincias join ciudad in listaCiudades on provincia equals ciudad.Localizacion select new { provincia, ciudad };
Tiene un extraño parecido a un Inner Join en SQL ¿Verdad? :)
En ambos casos el resultado podría ser del tipo:
Cádiz - Algeciras
Cádiz - Jerez
Málaga - Ronda
Málaga - Churriana
Madrid - Alcobendas
Madrid – Fuenlabrada
4.2.2 Join + Comparador personalizado
4.2.2.1 Código necesario
Una lista de iniciales y otra de palabras:
List<string> listaIniciales = new List<string> { "A", "B", "C", "D" };
List<string> listaPalabras = new List<string> { "Arroz", "Antena",
"Barco", "Caballo", "Decir" };
Un comparador personalizado:
public class ComparadorIniciales : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) {
31 Operadores de LINQ
return obj[0].GetHashCode(); } }
4.2.2.2 Definición
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>( this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el operador estándar, simplemente nos da la posibilidad de usar un comparador personalizado.
En el anterior ejemplo, habíamos simplemente comparado 2 instancias de por si iguales, ahora vamos a poner un ejemplo donde necesitaremos un comparador personalizado.
Tenemos una lista con iniciales y una lista de palabras. Queremos hacer una unión donde cada inicial estará asociada a unas palabras que empiecen por dicha inicial:
var query = listaIniciales.Join(listaPalabras, inicial => inicial, palabra => palabra, (inicial, palabra) => new { inicial, palabra }, new ComparadorIniciales());
Si necesitas saber cómo crear y usar un comparador revisa la definición del operador GroupJoin.
El resultado es:
A - Arroz
A - Antena
B - Barco
C - Caballo
D – Decir
32 5. Concatenation (Concatenación)
5. Concatenation (Concatenación)
5.1 Concat
El operador Concat sirve para concatenar 2 secuencias.
5.1.1 Código necesario para los ejemplos
Un par de listas de nombres:
List<string> nombres1 = new List<string> { "Jesus", "Alvaro", "Manolo" }; List<string> nombres2 = new List<string> { "Rosi", "Amanda", "Julia" };
5.1.2 Concat
public static IEnumerable<TSource> Concat<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
Concat recibe como parámetro una secuencia que queremos concatenar a la secuencia que está ejecutando el operador. Devuelve una sola enumeración que contiene las dos enumeraciones usadas.
Por ejemplo, si queremos concatenar las dos listas de nombres anteriores en una sola enumeración:
IEnumerable<string> nombres = nombres1.Concat(nombres2);
El resultado sería:
Jesus
Alvaro
Manolo
Rosi
Amanda
Julia
33 Operadores de LINQ
6. Ordering (Ordenación)
6.1 OrderBy
El operador OrderBy ordena una secuencia de forma ascendente dada una clave.
6.1.1 Código necesario para los ejemplos
Una clase persona:
public class Persona { public string Nombre { get; set; } public int Edad { get; set; } public Persona(string nombre, int edad) { Nombre = nombre; Edad = edad; } }
Una lista de personas:
List<Persona> personas = new List<Persona> { new Persona("Rodriguez, Jesús", 24), new Persona("Perez, Juan", 15), new Persona("García, Javier", 45), new Persona("Toledo, María", 37) };
Un comparador personalizado:
public class OrdenaNombre : IComparer<string> { public int Compare(string x, string y) { string nombreX = x.Split()[1]; string nombreY = y.Split()[1]; return nombreX.CompareTo(nombreY); } }
34 6. Ordering (Ordenación)
6.1.2 OrderBy estándar
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
OrderBy toma como parámetro la clave que se usará para ordenar la secuencia. Devuelve una enumeración ordenada.
Por ejemplo, si queremos ordenar la lista de personas por el nombre, usaríamos este código:
IEnumerable<Persona> ascendente = personas.OrderBy( persona => persona.Nombre);
La clave en este caso es la propiedad Nombre del elemento de la secuencia.
Como alternativa, podemos usar la sintaxis de los query expressions:
IEnumerable<Persona> ascendente = from persona in personas orderby persona.Nombre select persona;
El resultado sería:
Nombre=García, Javier Edad=45
Nombre=Perez, Juan Edad=15
Nombre=Rodriguez, Jesús Edad=24
Nombre=Toledo, María Edad=37
6.1.3 OrderBy + Comparador personalizado:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer )
Aquí además de la clave que usaremos para ordenar, usaremos un comparador personalizado.
35 Operadores de LINQ
Por ejemplo, queremos ordenar otra vez usando la propiedad Nombre pero en este caso en vez de ordenar por el apellido vamos a ordenar por el nombre en sí.
Para ello cogemos el comparador OrdenaNombre que he adjuntado en el código que necesitamos para trabajar con este operador, con ello escribimos:
IEnumerable<Persona> ascendente = personas.OrderBy(persona => persona.Nombre, new OrdenaNombre());
El comparador extraerá el nombre de cada propiedad y los comparará.
El resultado es:
Nombre=García, Javier Edad=45
Nombre=Rodriguez, Jesús Edad=24
Nombre=Perez, Juan Edad=15
Nombre=Toledo, María Edad=37
6.2 OrderByDescending
El operador OrderByDescending ordena una secuencia de forma descendente dada una clave.
6.2.1 Código necesario para los ejemplos
Una clase persona:
public class Persona { public string Nombre { get; set; } public int Edad { get; set; } public Persona(string nombre, int edad) { Nombre = nombre; Edad = edad; } }
36 6. Ordering (Ordenación)
Una lista de personas:
List<Persona> personas = new List<Persona> { new Persona("Rodriguez, Jesús", 24), new Persona("Perez, Juan", 15), new Persona("García, Javier", 45), new Persona("Toledo, María", 37) };
Un comparador personalizado:
public class OrdenaNombre : IComparer<string> { public int Compare(string x, string y) { string nombreX = x.Split()[1]; string nombreY = y.Split()[1]; return nombreX.CompareTo(nombreY); } }
6.2.2 OrderByDescending estándar
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
OrderByDescending toma como parámetro la clave que se usará para ordenar la secuencia (de forma descendente). Devuelve una enumeración ordenada.
Por ejemplo, si queremos ordenar la lista de personas por el nombre (de forma descendente), usaríamos este código:
IEnumerable<Persona> descendente = personas.OrderByDescending(persona => persona.Nombre);
La clave en este caso es la propiedad Nombre del elemento de la secuencia.
37 Operadores de LINQ
Como alternativa, podemos usar la sintaxis de los query expressions:
IEnumerable<Persona> descendente = from persona in personas orderby persona.Nombre descending select persona;
El resultado sería:
Nombre=Toledo, María Edad=37
Nombre=Rodriguez, Jesús Edad=24
Nombre=Perez, Juan Edad=15
Nombre=García, Javier Edad=45
6.2.3 OrderByDescending + Comparador personalizado
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer )
Aquí además de la clave que usaremos para ordenar, usaremos un comparador personalizado.
Por ejemplo, queremos ordenar otra vez usando la propiedad Nombre pero en este caso en vez de ordenar por el apellido vamos a ordenar por el nombre en sí.
Para ello cogemos el comparador OrdenaNombre que he adjuntado en el código que necesitamos para trabajar con este operador, con ello escribimos:
IEnumerable<Persona> descendente = personas.OrderByDescending(persona => persona.Nombre,
new OrdenaNombre());
El comparador extraerá el nombre de cada propiedad y los comparará.
El resultado es:
Nombre=Toledo, María Edad=37
Nombre=Perez, Juan Edad=15
Nombre=Rodriguez, Jesús Edad=24
Nombre=García, Javier Edad=45
38 6. Ordering (Ordenación)
6.3 Reverse
El operador Reverse invierte una secuencia.
6.3.1 Código necesario para los ejemplos
Una lista de cadenas representando números:
List<string> numeros = new List<string> { "Uno", "Dos", "Tres", "Cuatro", "Cinco" };
6.3.2 Reverse
public static IEnumerable<TSource> Reverse<TSource>( this IEnumerable<TSource> source )
Este operador no toma ningún parámetro cuando lo usas en una secuencia (ya que el parámetro que empieza por this indica que es un método de extensión).
Cuando lo usas como un método de extensión en una secuencia, modifica la misma secuencia en sí pero no devuelve nada.
Por ejemplo:
numeros.Reverse();
Eso le daría la vuelta a la lista de números directamente.
O sea:
Cinco
Cuatro
Tres
Dos
Uno
39 Operadores de LINQ
6.4 ThenBy
El operador ThenBy toma una enumeración ordenada y le hace una ordenación adicional usando otra clave.
6.4.1 Código necesario para los ejemplos
Una clase persona:
public class Persona { public string Nombre { get; set; } public int Edad { get; set; } public Persona(string nombre, int edad) { Nombre = nombre; Edad = edad; } }
Una lista de personas:
List<Persona> personas = new List<Persona> { new Persona("Rodriguez, Jesús", 24), new Persona("Rodriguez, Jesús", 15), new Persona("Perez, Juan", 15), new Persona("García, Javier", 24), new Persona("Toledo, María", 37) };
Un comparador personalizado:
public class OrdenaNombre : IComparer<string> { public int Compare(string x, string y) { string nombreX = x.Split()[1]; string nombreY = y.Split()[1]; return nombreX.CompareTo(nombreY); } }
40 6. Ordering (Ordenación)
6.4.2 ThenBy estándar
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador ThenBy toma como argumento la clave que se usará para la ordenación adicional. Devuelve la enumeración ordenada.
Por ejemplo, queremos ordenar la lista de personas primero por su nombre y como ordenación adicional, la edad:
IEnumerable<Persona> dobleOrdenación = personas.OrderBy( persona => persona.Nombre).ThenBy(persona => persona.Edad);
Estamos usado lo que se llama Encadenar operadores esto quiere decir:
Llamamos a OrderBy desde personas, OrderBy devuelve un IOrderedEnumerable (una enumeración ordenada).
ThenBy es un método de extensión de dicho IOrderedEnumerable así que a eso le aplicamos ThenBy.
Dicho de otra forma, ordenamos primero por nombre y luego ordenamos por edad.
También podemos hacer esto usando una query expression:
IEnumerable<Persona> dobleOrdenacion = from persona in personas orderby persona.Nombre, persona.Edad
select persona;
En ambos casos, el resultado es:
Nombre=García, Javier Edad=24
Nombre=Perez, Juan Edad=15
Nombre=Rodriguez, Jesús Edad=15
Nombre=Rodriguez, Jesús Edad=24
Nombre=Toledo, María Edad=37
41 Operadores de LINQ
6.4.3 ThenBy + Comparador personalizado
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer )
Aquí además de la clave, recibimos una instancia de un comparador personalizado.
Vamos a ordenar primero por Edad (de forma descendente) y luego por Nombre pero usando el nombre y no el apellido para ordenar. Para ello usaremos una instancia del comparador adjunto con el código:
IEnumerable<Persona> dobleOrdenacion = personas.OrderByDescending(persona => persona.Edad) .ThenBy(persona => persona.Nombre, new OrdenaNombre());
Como puedes ver, ThenBy trabaja con una enumeración ordenada, pero le da igual si está ordenada ascendentemente o descendentemente.
El resultado es:
Nombre=Toledo, María Edad=37
Nombre=García, Javier Edad=24
Nombre=Rodriguez, Jesús Edad=24
Nombre=Rodriguez, Jesús Edad=15
Nombre=Perez, Juan Edad=15
42 6. Ordering (Ordenación)
6.5 ThenByDescending
El operador ThenByDescending toma una enumeración ordenada y le hace una ordenación adicional descendente usando otra clave.
6.5.1 Código necesario para los ejemplos
Una clase persona:
public class Persona { public string Nombre { get; set; } public int Edad { get; set; } public Persona(string nombre, int edad) { Nombre = nombre; Edad = edad; } }
Una lista de personas:
List<Persona> personas = new List<Persona> { new Persona("Rodriguez, Jesús", 24), new Persona("Rodriguez, Jesús", 15), new Persona("Perez, Juan", 15), new Persona("García, Javier", 24), new Persona("Toledo, María", 37) };
Un comparador personalizado:
public class OrdenaNombre : IComparer<string> { public int Compare(string x, string y) { string nombreX = x.Split()[1]; string nombreY = y.Split()[1]; return nombreX.CompareTo(nombreY); } }
43 Operadores de LINQ
6.5.2 ThenByDescending estándar
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador ThenByDescending toma como argumento la clave que se usará para la ordenación adicional (que será descendiente). Devuelve la enumeración ordenada.
Por ejemplo, queremos ordenar la lista de personas primero por su nombre (ascendente) y como ordenación adicional, la edad (descendente):
IEnumerable<Persona> dobleOrdenacion = personas.OrderBy(persona => persona.Nombre).ThenByDescending(persona => persona.Edad);
Estamos usado lo que se llama Encadenar operadores esto quiere decir:
Llamamos a OrderBy desde personas, OrderBy devuelve un IOrderedEnumerable (una enumeración ordenada).
ThenByDescending es un método de extensión de dicho IOrderedEnumerable así que a eso le aplicamos ThenByDescending.
Dicho de otra forma, ordenamos primero por nombre y luego ordenamos por edad.
También podemos hacer esto usando una query expression:
IEnumerable<Persona> dobleOrdenacion = from persona in personas orderby persona.Nombre, persona.Edad descending
select persona;
En ambos casos, el resultado es:
Nombre=García, Javier Edad=24
Nombre=Perez, Juan Edad=15
Nombre=Rodriguez, Jesús Edad=24
Nombre=Rodriguez, Jesús Edad=15
Nombre=Toledo, María Edad=37
44 6. Ordering (Ordenación)
6.5.3 ThenByDescending + Comparador personalizado
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>( this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer )
Aquí además de la clave, recibimos una instancia de un comparador personalizado.
Vamos a ordenar primero por Edad y luego por Nombre (Descendente) pero usando el nombre y no el apellido para ordenar. Para ello usaremos una instancia del comparador adjunto con el código:
IEnumerable<Persona> dobleOrdenacion = personas.OrderBy( persona => persona.Edad).ThenByDescending( persona => persona.Nombre, new OrdenaNombre());
El resultado es:
Nombre=Perez, Juan Edad=15
Nombre=Rodriguez, Jesús Edad=15
Nombre=Rodriguez, Jesús Edad=24
Nombre=García, Javier Edad=24
Nombre=Toledo, María Edad=37
45 Operadores de LINQ
7. Grouping (Agrupación)
7.1 GroupBy
El operador GroupBy nos permite agrupar una secuencia bajo una clave.
7.1.1 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") };
Un comparador personalizado:
public class MayoriaEdad : IEqualityComparer<int> { public bool Equals(int x, int y) { return (EsMayor(x) == EsMayor(y)); } public int GetHashCode(int edad) { int menor = 1; int mayor = 18;
46 7. Grouping (Agrupación)
return (EsMayor(edad) ? mayor.GetHashCode() : menor.GetHashCode());
} public bool EsMayor(int edad) { return (edad >= 18); } }
Un método de ayuda:
public string MayorMenor(int edad) { return edad < 18 ? "Menores de edad" : "Mayores de edad"; }
7.1.2 GroupBy estándar
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador GroupBy estándar simplemente recibe un delegado al que le pasamos la clave que usaremos para agrupar los elementos de la secuencia. Devuelve una enumeración de elementos que tienen una clave común.
Queremos agrupar nuestra lista de personas en grupos donde la clave sea la ciudad. Para ello debemos de indicarle a GroupBy que como clave queremos usar ciudad:
IEnumerable<IGrouping<string, Persona>> personasPorCiudad = personas.GroupBy(persona => persona.Ciudad);
Nada difícil, le decimos que queremos agrupar la secuencia por la ciudad.
También podemos agrupar usando un query expression:
IEnumerable<IGrouping<string, Persona>> personasPorCiudad = from persona in personas group persona by persona.Ciudad into perCiudad select perCiudad;
Por cada persona agrupamos dicha persona por su ciudad en perCiudad y simplemente devolvemos perCiudad.
47 Operadores de LINQ
En ambos casos podríamos imprimir el resultado de esta forma:
foreach (var ciudad in personasPorCiudad) { Console.WriteLine(ciudad.Key); foreach (var persona in ciudad) { Console.WriteLine("{0} ({1})", persona.Nombre, persona.Edad); } Console.WriteLine(); }
Y obtener algo así:
Cádiz
Rodriguez, Jesús (24)
Perez, Juan (15)
Alicante
Bautista, Jesús (15)
Málaga
García, Javier (24)
Toledo, María (37)
7.1.3 GroupBy + comparador personalizado
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga hace lo mismo que el GroupBy estándar con la diferencia de que usa un comparador personalizado a la hora de agrupar los elementos.
48 7. Grouping (Agrupación)
Hemos creado un comparador personalizado, el cual he adjuntado en el código de este operador, que nos sirve para agrupar las personas por mayores o menores de edad.
IEnumerable<IGrouping<int, Persona>> personasPorMayoria = personas.GroupBy(p => p.Edad, new MayoriaEdad());
Ahora tenemos la secuencia agrupadas por mayores de edad y por menores de edad.
Algo así:
Mayor
24 - Rodriguez, Jesús (Cádiz)
24 - García, Javier (Málaga)
37 - Toledo, María (Málaga)
Menor
15 - Bautista, Jesús (Alicante)
15 - Perez, Juan (Cádiz)
7.1.4 GroupBy + proyector sencillo
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector )
En esta sobrecarga podemos usar una función para proyectar los elementos de cada grupo.
49 Operadores de LINQ
Por ejemplo, vamos a agrupar por la ciudad pero esta vez solo queremos proyectar el nombre, no necesitamos la edad para nada:
IEnumerable<IGrouping<string, string>> perCiudad = personas.GroupBy(p => p.Ciudad, p => p.Nombre);
La diferencia con el operador estándar es que ya no estamos proyectando todo el objeto Persona, sólo estamos proyectando el nombre.
El resultado sería:
Cádiz
Rodriguez, Jesús
Perez, Juan
Alicante
Bautista, Jesús
Málaga
García, Javier
Toledo, María
7.1.5 GroupBy + proyector de enumeración
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector )
En esta sobrecarga, vamos a agrupar los elementos bajo una clave común pero usamos un segundo delegado el cual recibe la clave, una enumeración con los elementos agrupados bajo dicha clave y devuelve un elemento como resultado. Finalmente el operador devuelve una enumeración de dichos resultados.
50 7. Grouping (Agrupación)
Como es algo complicado de entender (y aún más de explicar sin ejemplos) vamos con un ejemplo. Vamos a agrupar por ciudad y lo que queremos saber es cuantas personas de nuestra lista residen en dichas ciudades:
var infoCiudades = personas.GroupBy(p => p.Ciudad, (ciudad, perCiudad) => new { Ciudad = ciudad, NumPersonas = perCiudad.Count() });
Le hemos indicado que queremos agrupar por ciudad, y luego hemos creado un tipo anónimo que contiene la clave y la cantidad de personas por cada ciudad.
Ahora si volvemos a leer la descripción de esta sobrecarga, nos queda más claro. Estamos creando una enumeración de tipos anónimos compuestos por la ciudad y por la cantidad de personas de dicha ciudad.
El resultado es:
Ciudad=Cádiz NumPersonas=2
Ciudad=Alicante NumPersonas=1
Ciudad=Málaga NumPersonas=2
7.1.6 GroupBy + proyector sencillo + comparador personalizado
public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer )
Ésta sobrecarga es una mezcla de 2 sobrecargas que ya hemos visto. Agrupa los elementos dada una clave, proyecta la parte que necesitamos de cada elemento agrupado y para hacer la agrupación.
Así que vamos a hacer una mezcla entre los dos ejemplos de dichas sobrecargas. Vamos a agrupar por mayores o menores de edad y luego
51 Operadores de LINQ
de los elementos agrupados, solo vamos a proyectar el nombre (descartando la ciudad y la edad):
IEnumerable<IGrouping<int, string>> porMayoria = personas.GroupBy(p => p.Edad, p => p.Nombre, new MayoriaEdad());
El resultado sería algo así:
Mayor
Rodriguez, Jesús
García, Javier
Toledo, María
Menor
Bautista, Jesús
Perez, Juan
7.1.7 GroupBy + proyector sencillo + proyector de enumeración
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector )
Esta sobrecarga, agrupa los elementos dada una clave, proyecta solo la parte que necesitamos de cada elemento agrupado y por último pasamos cada clave y cada proyección a otro delegado el cual crea un elemento resultante. El operador devuelve una enumeración de dicho elemento.
52 7. Grouping (Agrupación)
Vamos a volver a volver a agrupar a las personas por ciudad y vamos a contar cuantas personas hay en cada ciudad:
var perCiudad = personas.GroupBy(p => p.Ciudad, p => p.Ciudad, (key, ciudades) => new { Ciudad = key, NumPersonas = ciudades.Count() });
El ejemplo es el mismo que hemos usado en la sobrecarga del proyector de enumeración. La diferencia es que aquí en vez de pasarle una enumeración de personas le he pasado una enumeración de cadenas.
La idea es que como no necesitamos toda la información que nos provee la clase persona, proyectamos simplemente aquella parte que necesitamos (aunque puntualizando, aquí realmente no necesitamos ni la edad, ni el nombre ni la ciudad, pero tenemos que elegir al menos un elemento a proyectar).
El resultado es el mismo:
Ciudad=Cádiz NumPersonas=2
Ciudad=Alicante NumPersonas=1
Ciudad=Málaga NumPersonas=2
7.1.8 GroupBy + proyector de enumeración + comparador personalizado
public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga tiene el delegado de enumeración que ya hemos visto en dos ocasiones y además soporta un comparador personalizado.
53 Operadores de LINQ
Vamos a agrupar por mayoría o minoría de edad (usando nuestro comparador) y luego vamos a proyectar la cantidad de personas en cada grupo:
var infoEdades = personas.GroupBy(p => p.Edad, (edad, pers) => new { Condicion = MayorMenor(edad), NumPersonas = pers.Count() }, new MayoriaEdad());
Bueno, nada nuevo realmente por aquí. El comparador siempre usa la clave para comparar y simplemente sacamos información de cada grupo.
El resultado es:
Mayores de edad
Numero personas: 3
Menores de edad
Numero personas: 2
7.1.9 GroupBy + proyector simple + proyector de enumeración + comparador personalizado
public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer )
He aquí la combinación de todas las sobrecargas de este pequeño operador. Tiene su proyector de elementos, proyecta luego una enumeración y todo con su comparador personalizado.
54 7. Grouping (Agrupación)
Vamos a agrupar por mayoría o minoría de edad, y vamos a sacar varios datos sobre dichas edades:
var infoEdades = personas.GroupBy(p => p.Edad, p => p.Edad, (edad, edades) => new { Condicion = MayorMenor(edad), Min = edades.Min(), Max = edades.Max() }, new MayoriaEdad());
Agrupamos por edad, proyectamos solo la edad puesto que es lo único que necesitamos, guardamos la edad mínima y máxima del grupo y usamos el comparador personalizado para hacer las agrupaciones.
El resultado es:
Mayores de edad
Edad mínima: 24
Edad máxima: 37
Menores de edad
Edad mínima: 15
Edad máxima: 15
55 Operadores de LINQ
7.2 ToLookup
El operador ToLookup es bastante parecido a GroupBy en cuanto uso, la diferencia es que este operador crea un objeto del tipo ToLookup como veremos en los ejemplos.
7.2.1 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") };
Un comparador personalizado:
public class MayoriaEdad : IEqualityComparer<int> { public bool Equals(int x, int y) { return (EsMayor(x) == EsMayor(y)); } public int GetHashCode(int edad) { int menor = 1; int mayor = 18;
56 7. Grouping (Agrupación)
return (EsMayor(edad) ? mayor.GetHashCode() : menor.GetHashCode());
} public bool EsMayor(int edad) { return (edad >= 18); } }
7.2.2 ToLookup estándar
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector )
El operador ToLookup recibe un delegado que usaremos para seleccionar la clave. Este operador devuelve un objeto que implemente la interfaz ILookup<TKey, TSource>.
ILookup implementa a su vez IEnumerable<IGrouping<TKey, TElement>>. Lo cual podemos deducir que este operador hace lo que hace GroupBy pero con funcionalidad extra. La idea está en que este operador nos crea un objeto al cual podemos hacerle consultas. Para que quede más claro vamos con un ejemplo:
ILookup<string, Persona> perCiudad = personas.ToLookup( p => p.Ciudad);
Aquí creamos un objeto que implementa la interfaz ILookup. En este caso la clave es una cadena (la ciudad) y como elemento contiene una enumeración de personas (que vivan en esa ciudad).
¿Es lo mismo que GroupBy, no? No exactamente, Imaginemos que necesitamos las personas que sean de Cádiz, pues ahora podemos hacer:
IEnumerable<Persona> gaditanos = perCiudad["Cádiz"];
Como podéis ver, el objeto tiene un indizador, así que podemos acceder aquellos elementos que queramos simplemente especificando la clave.
Imprimir esa variable daría algo del tipo:
Nombre=Rodriguez, Jesús Edad=24 Ciudad=Cádiz
Nombre=Perez, Juan Edad=15 Ciudad=Cádiz
57 Operadores de LINQ
7.2.3 ToLookup + comparador personalizado
public static ILookup<TKey, TSource> ToLookup<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga toma además un comparador personalizado para seleccionar la clave. Veámoslo con un ejemplo:
ILookup<int, Persona> mayorOMenor = personas.ToLookup(p => p.Edad, new MayoriaEdad());
Ésta es la parte sencilla de entender. Creamos un objeto que implementa ILookup y para comparar las claves usamos nuestro comparador personalizado.
Ahora la parte difícil de entender:
IEnumerable<Persona> mayores = mayorOMenor[20];
No hay nadie con 20 años, ¿no? Es cierto, pero no es lo que le estamos pidiendo.
Como sabréis, la mayoría de operadores de LINQ implementan la ejecución diferida. ¿Qué significa esto? Que cuando ejecutas el operador no estás realmente creando ninguna colección ni nada. Lo que obtienes del operador es un objeto que almacena la información necesaria para realizar la acción de dicho operador.
Así que cuando ejecutamos la sentencia anterior, lo que va a hacer es enviar el valor del indizador (en nuestro caso 20) al comparador, ahí va a compararlo con todas las edades. Algo así:
Toma la edad que le hemos pasado (20) y comprueba que es una edad sobre 18. Luego va tomando cada valor de la lista. Por ejemplo 15, no es mayor de edad así que descartada, luego coge 24, también es mayor de edad, y como ambas lo son, devuelve la persona con dicha edad.
En resumen, con esta sobrecarga, puedes pasarle cualquier número al objeto ILookup que si es mayor de 18 (mayoría de edad) devolverá todos los mayores de edad, y si es un número menor a 18, devolverá aquellos que no lo son. Con la otra sobrecarga solo podías meterle valores específicos para que funcionase.
58 7. Grouping (Agrupación)
Así que si le pasamos 20 conseguiremos:
Nombre=Rodriguez, Jesús Edad=24 Ciudad=Cádiz
Nombre=García, Javier Edad=24 Ciudad=Málaga
Nombre=Toledo, María Edad=37 Ciudad=Málaga
7.2.4 ToLookup + proyector
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector )
En esta sobrecarga tenemos la posibilidad de definir como serán los elementos asociados a las claves.
Volviendo al ejemplo de agrupar por ciudades. Nosotros agrupábamos por ciudad, luego le decíamos que queríamos todas aquellas personas que fueran de Cádiz. El "problema" es que cada objeto que nos devuelve también contiene una propiedad del tipo Ciudad lo cual es redundante, puesto que ya sabemos que son de Cádiz.
Podríamos solucionar eso de la siguiente forma:
var perCiudad = personas.ToLookup(p => p.Ciudad, p => new { p.Nombre, p.Edad });
Hemos creado un objeto que implementa ILookup pero esta vez, aparte de agrupar por ciudad, hemos creado un tipo anónimo que solo contiene el nombre y la edad descartando la ciudad (pues ya la sabemos a la hora de hacer una consulta).
Se usaría como siempre:
var gaditanos = perCiudad["Cádiz"];
Y el resultado:
Nombre=Rodriguez, Jesús Edad=24
Nombre=Perez, Juan Edad=15
59 Operadores de LINQ
7.2.5 ToLookup + proyector + comparador personalizado
public static ILookup<TKey, TElement> ToLookup<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer )
Esta sobrecarga es una mezcla entre las 3 anteriores. Agrupamos dada una clave, proyectamos aquello que necesitamos y usamos un comparador personalizado para la agrupación.
Vamos a agrupar por edad como hicimos antes, proyectamos solo nombre - ciudad y usamos nuestro comparador:
var mayorOMenor = personas.ToLookup(p => p.Edad, p => new { p.Nombre, p.Ciudad }, new MayoriaEdad());
Y pedimos a todos los menores (se puede poner cualquier edad que sea menor a 18):
var menores = mayorOMenor[17];
El resultado es:
Nombre=Bautista, Jesús Ciudad=Alicante
Nombre=Perez, Juan Ciudad=Cádiz
60 8. Set (Conjunto)
8. Set (Conjunto) 8.1 Distinct
El operador Distinct sirve para eliminar elementos duplicados de una secuencia.
8.1.1 Código necesario para los ejemplos
Una lista de ciudades (con algunas repetidas):
List<string> ciudades = new List<string>{"Cádiz", "Málaga", "Murcia", "Málaga", "Madrid", "Barcelona", "Galicia", "Bilbao", "Cádiz"};
Un comparador de iniciales:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
8.1.2 Distinct estándar
public static IEnumerable<TSource> Distinct<TSource>( this IEnumerable<TSource> source )
El operador Distinct no toma ningún parámetro, solo la secuencia que usará el operador, pero eso es implícito. Devuelve una enumeración donde ya no hay entradas repetidas.
Veámoslo en un ejemplo:
IEnumerable<string> ciudadesUnicas = ciudades.Distinct();
61 Operadores de LINQ
¿Difícil? Claro que no. Como es lógico, la enumeración contiene:
Cádiz
Málaga
Murcia
Madrid
Barcelona
Galicia
Bilbao
Ninguna ciudad repetida :).
8.1.3 Distinct + comparador personalizado
public static IEnumerable<TSource> Distinct<TSource>( this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer )
Ahora tenemos la posibilidad de comparar cada elemento de la secuencia de una forma personalizada.
Por ejemplo, consideraremos una ciudad como repetida cuando comience con la misma letra que otra ya existente. Por ejemplo, si ya está Barcelona que empieza por B, no queremos a Bilbao puesto que empieza por B también.
Para ello usaremos el comparador adjunto:
IEnumerable<string> ciudadesUnicas = ciudades.Distinct( new ComparadorInicial());
El resultado como cabe de esperar es:
Cádiz
Málaga
Barcelona
Galicia
62 8. Set (Conjunto)
8.2 Except
El operador Except proporciona la diferencia de conjuntos de dos secuencias.
8.2.1 Código necesario para los ejemplos
Una lista de ciudades (con algunas repetidas):
List<string> ciudades = new List<string>{"Cádiz", "Málaga", "Murcia", "Málaga", "Madrid", "Barcelona", "Galicia", "Bilbao", "Cádiz"};
8. Set (Conjunto)
Una lista de ciudades a descartar:
List<string> aDescartar = new List<string> { "Galicia", "Murcia" };
Una lista de iniciales a descartar:
List<string> inicialDescartar = new List<string> { "C", "M" };
Un comparador de iniciales:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
8.2.2 Except estándar
public static IEnumerable<TSource> Except<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
El operador Except toma como parámetro otra secuencia del mismo tipo que usará para descartar elementos. Devuelve una enumeración con los elementos no descartados.
63 Operadores de LINQ
Vamos a explicarlo mejor. El operador recorre la primera lista y va descartando los elementos repetidos (tal y como hace Distinct). Una vez ha recorrido la primera secuencia, empieza a recorrer la segunda. Ahora descartará todo elemento que aparezca en las dos secuencias. Veámoslo con un ejemplo:
IEnumerable<string> ciudadesFiltradas = ciudades.Except(aDescartar);
Tenemos una secuencia que contiene varias ciudades. Empieza descartando las repetidas (Como Málaga, Cádiz...). Cuando termina de recorrer la primera secuencia, empieza a recorrer la segunda, la cual contiene el nombre de dos ciudades. Cogerá la primera, Galicia, si existe en la primera secuencia pues la descarta de dicha secuencia, si no, simplemente se ignora. Así con el resto.
Entonces, una vez descartadas las repetidas y descartadas aquellas que coincidan en las dos secuencias, nos quedaría:
Cádiz
Málaga
Madrid
Barcelona
Bilbao
8.2.3 Except + comparador personalizado
public static IEnumerable<TSource> Except<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
Ahora tenemos la posibilidad de usar un comparador personalizado a la hora de comprobar qué elementos tenemos que descartar. Veamos un ejemplo y analicémoslo:
IEnumerable<string> ciudadesFiltradas = ciudades.Except(inicialDescartar, new ComparadorInicial());
inicialDescartar contiene una C y una M. Según el comparador que hemos usado, ¿Descartará todas las C y todas las M? No exactamente...
Recordad cómo trabaja el operador.
64 8. Set (Conjunto)
Primero recorre la primera secuencia comparando cada elemento usando el comparador proporcionado. Eso hará que cuando una palabra empiece por una letra, las otras que empiecen por dicha letra serán descartadas directamente. Así que cuando encuentra Barcelona ya descartará Bilbao por empezar por B también. Una vez recorre la primera secuencia, empieza con la segunda y ya directamente descarta de la primera secuencia todas las palabras que empiecen por C y por M.
Así que la cosa terminaría así:
Barcelona
Galicia
¿Sorprendido? Claro que no, descartó todas las que empezaban por C y por M además de Bilbao puesto que ya había una que empezara por B (Barcelona).
8.3 Intersect
El operador Intersect proporciona la intersección de conjuntos de dos secuencias. O lo que es lo mismo, aquellos elementos que coincidan en las dos secuencias.
8.3.1 Código necesario para los ejemplos
Una lista de ciudades:
List<string> ciudades1 = new List<string>{"Cádiz", "Málaga", "Murcia", "Madrid", "Barcelona", "Galicia", "Bilbao"};
Otra lista de ciudades:
List<string> ciudades2 = new List<string> { "Cádiz", "Málaga", "Bilbao", "Granada", "Pamplona" };
65 Operadores de LINQ
Un comparador de iniciales:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
8.3.2 Intersect estándar
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
Intersect toma como parámetro una secuencia que se usará a la hora de hacer la intersección. Devuelve una enumeración con aquellos elementos que coincidan en ambas secuencias.
Por ejemplo, si queremos crear una enumeración con las ciudades en común de las dos listas de ciudades que tenemos:
IEnumerable<string> ciudades = ciudades1.Intersect(ciudades2);
Nada difícil, el resultado es:
Cádiz
Málaga
Bilbao
Esas tres ciudades coinciden en ambas listas :)
66 8. Set (Conjunto)
8.3.3 Intersect + comparador personalizado
public static IEnumerable<TSource> Intersect<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
Como viene siendo costumbre, además de nuestra secuencia para la intersección, podemos usar un comparador personalizado.
Vamos con un ejemplo (es muy estúpido lo sé, pero los comparadores personalizados se usan en casos muy extremos, y es difícil encontrar ejemplos simples) que nos va ayudar a entender cómo funciona este operador.
IEnumerable<string> ciudades = ciudades1.Intersect(ciudades2, new ComparadorInicial());
¿Qué debería de devolver? Piénsalo dos veces antes de probarlo.
Ahora bien, ¿Qué devuelve realmente?:
Cádiz
Málaga
Barcelona
Galicia
¿Y Bilbao? ¿Y Galicia? ¿Por qué no salen?
Vamos paso por paso:
Aquí lo importante es la inicial, nada más, entonces, ve que en la primera secuencia hay una C y que en la segunda también. Así que agrega la palabra de la primera secuencia que contiene dicha C, en este caso, Cádiz. ¿Qué pasa cuando llega a la B de Barcelona? Que comprueba si existe otra B en la segunda secuencia y sí, hay otra, la de Bilbao. ¿Pero qué pasa? Que como antes, agrega el elemento de la primera secuencia, en este caso Barcelona ignorando Bilbao. Lo mismo pasa con Galicia y Granada.
67 Operadores de LINQ
8.4 Union
El operador Union proporciona la unión de conjuntos de dos secuencias. O lo que es lo mismo, mezcla los elementos de dos secuencias.
8.4.1 Código necesario para los ejemplos
Una lista de ciudades:
List<string> ciudades1 = new List<string>{"Cádiz", "Málaga", "Murcia", "Madrid", "Barcelona", "Galicia", "Bilbao"};
Otra lista de ciudades:
List<string> ciudades2 = new List<string> { "Cádiz", "Málaga", "Bilbao", "Granada", "Pamplona" };
Un comparador de iniciales:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
8.4.2 Union estándar
public static IEnumerable<TSource> Union<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
El operador Union toma como parámetro la otra secuencia que usará para la unión. Devuelve una enumeración con la unión de las dos secuencias.
68 8. Set (Conjunto)
Tenemos dos secuencias con ciudades, queremos crear una enumeración que contenga las ciudades de ambas secuencias (sin repetir):
IEnumerable<string> ciudades = ciudades1.Union(ciudades2);
Nada que deba sorprendernos. El resultado:
Cádiz
Málaga
Murcia
Madrid
Barcelona
Galicia
Bilbao
Granada
Pamplona
8.4.3 Union + comparador personalizado
public static IEnumerable<TSource> Union<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
Como todos los operadores de esta familia, tenemos la sobrecargar con el comparador personalizado.
Vamos a hacer una unión por iniciales:
IEnumerable<string> ciudades = ciudades1.Union(ciudades2, new ComparadorInicial());
Básicamente creará una enumeración donde se va devolviendo cada palabra siempre y cuando no haya otra que tenga la misma inicial. Como en los casos anteriores en esta familia. Primero se devuelve las palabras de la primera secuencia y luego de la segunda. Así que Bilbao por ejemplo no aparece puesto que se devolvió primero Barcelona.
69 Operadores de LINQ
El resultado es:
Cádiz
Málaga
Barcelona
Galicia
Pamplona
70 9. Conversion (Conversión)
9. Conversion (Conversión)
9.1 AsEnumerable
El operador AsEnumerable convierte una secuencia en una enumeración del tipo IEnumerable<T>.
9.1.1 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.
9.1.2 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<T> y para ello necesita un... ¿IEnumerable<T>? Los operadores estándar son los de LINQ to Objects y esos son miembros de IEnumerable<T>. 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áis pensando. Podemos convertir nuestra secuencia IQueryable<T> por una IEnumerable<T> y ya poder usar esos operadores que faltan o las implementaciones de IEnumerable<T> de los operadores.
71 Operadores de LINQ
¿Cómo? Así:
IEnumerable<Persona> ienupersonas = personas.AsEnumerable();
Con esto hemos convertido de IQueryable<T> a IEnumerable<T>. Hemos podido usar ese método en el IQueryable<T> puesto que esta interfaz implementa IEnumerable<T>.
9.2 AsQueryable
El operador AsQueryable convierte una enumeración del tipo IEnumerable en una del tipo IQueryable<T>
9.2.1 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();
9.2.2 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 comprensió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.
72 9. Conversion (Conversión)
9.3 Cast
El operador Cast puede convertir un array estándar a un objeto que implementa IEnumerable<T>.
9.3.1 Código necesario para los ejemplos
Un array de enteros:
int[] arrayNum = { 1, 2, 3, 4, 5 };
9.3.2 Cast
public static IEnumerable<TResult> Cast<TResult>( this IEnumerable source )
El operador Cast se usa con la secuencia que queremos convertir a un tipo. 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<int>? Hay una razón sencilla: Un array normal y corriente no implementa IEnumerable<T> lo que conlleva a no poder usar ningún operador de LINQ. Así que si convertimos nuestro array en un IEnumerable<T> 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.
73 Operadores de LINQ
El resultado de esto último sería:
1 4 9 16 25
9.4 ToArray
El operador ToArray convierte una enumeración del tipo IEnumerable<T> en un array.
9.4.1 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") };
74 9. Conversion (Conversión)
9.4.2 ToArray
public static TSource[] ToArray<TSource>( this IEnumerable<TSource> source )
Éste operador toma como parámetro un objeto IEnumerable<T> de un tipo y devuelve un array de dicho tipo. Por ejemplo, tenemos una lista 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
9.5 ToDictionary
El operador ToDictionary convierte una enumeración del tipo IEnumerable en un Dictionary<TKey, TValue>.
9.5.1 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; }
75 Operadores de LINQ
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(); } }
9.5.2 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.
76 9. Conversion (Conversión)
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"];
9.5.3 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, ahí 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.
77 Operadores de LINQ
9.5.4 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 cuál 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 imprimiéramos 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]
9.5.5 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
78 9. Conversion (Conversión)
que nos devuelva un nombre, comprobará cada edad con nuestro comparador personalizado.
9.6 ToList
El operador ToList convierte una enumeración del tipo IEnumerable<T> en un objeto del tipo List<T>.
9.6.1 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") };
9.6.2 ToList
public static List<TSource> ToList<TSource>( this IEnumerable<TSource> source )
Recibe como parámetro el objeto tipo IEnumerable<T> a convertir. Devuelve un objeto List de dicho tipo.
79 Operadores de LINQ
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
80 10. Equality (Igualdad)
10. Equality (Igualdad)
10.1 SequenceEqual
El operador SequenceEqual sirve para comprobar la igualdad entre dos secuencias
10.1.1 Código necesario para los ejemplos
Dos listas con el mismo contenido:
List<string> lista1 = new List<string> { "Abuelo", "Madre", "Tio", "Sobrina" }; List<string> lista2 = new List<string> { "Abuelo", "Madre", "Tio", "Sobrina" };
Una lista con contenido distinto:
List<string> listaDis = new List<string> { "Agencia", "Mina", "Tronco", "Sandalia" };
Un comparador personalizado:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
10.1.2 SequenceEqual estándar
public static bool SequenceEqual<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second )
Una secuencia llamará a este operador pasando una segunda secuencia como parámetro. Se comparará cada elemento de la secuencia usando el comparador por defecto. Devolverá un bool indicándonos si son iguales o no las secuencias.
81 Operadores de LINQ
Por ejemplo, vamos a comprar las dos listas que tenemos en con el mismo contenido:
bool igual = lista1.SequenceEqual(lista2);
Irá comparando cada elemento usando el comparador por defecto de dicho elemento, en este caso el comparador de las cadenas. Como las listas tienen el mismo contenido, pues dará:
true
¿Qué pasa si la comparamos con la lista distinta?
bool igual = lista1.SequenceEqual(listaDis);
Auch, ahora el resultado es:
false
Aunque no debería de sorprendernos.
10.1.3 SequenceEqual + comparador personalizado
public static bool SequenceEqual<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer )
Ahora podremos comparar los elementos de las secuencias usando un comparador personalizado. Vamos a comparar usando solo la inicial:
bool igual = lista1.SequenceEqual(listaDis, new ComparadorInicial());
Aquí comparará los elementos de la lista1 y de la lista2 usando nuestro comparador personalizado. En otras palabras comprobará la inicial de cada palabra, y como coinciden, el resultado es:
true
82 11. Element (Elemento)
11. Element (Elemento)
11.1 ElementAt
Con el operador ElementAt podemos extraer un elemento de una secuencia dado su índice.
11.1.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
11.1.2 ElementAt
public static TSource ElementAt<TSource>( this IEnumerable<TSource> source, int index )
El operador toma un int que será el índice (empezado desde 0) del elemento que queremos extraer. Si quisiéramos extraer el lenguaje Python, el índice sería el 2:
string python = lenguajes.ElementAt(2);
Lo que imprimiría:
Python
11.2 ElementAtOrDefault
El operador ElementAtOrDefault trabaja igual que su hermano ElementAt sólo que devolverá un valor predeterminado si no se encuentra dicho elemento.
83 Operadores de LINQ
11.2.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
11.2.2 ElementAtOrDefault
public static TSource ElementAtOrDefault<TSource>( this IEnumerable<TSource> source, int index )
El operador toma un int que será el índice (empezando desde 0) del elemento a extraer. Si dicho en dicho índice no existiera un elemento, devolverá el valor por defecto del tipo. Por ejemplo, si queremos extraer un string pero resulta que en ese índice no hay un string, devolvería el valor por defecto del tipo string. Lo mismo con cualquier otro tipo. Así que por ejemplo:
string nada = lenguajes.ElementAtOrDefault(20);
No daría una excepción de fuera de rango, simplemente nada sería null.
11.3 First
El operador First devuelve el primer elemento de una secuencia.
11.3.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
11.3.2 First estándar
public static TSource First<TSource>( this IEnumerable<TSource> source )
First no toma ningún parámetro. Simplemente ejecutarlo en la secuencia en la que queramos extraer el primer elemento.
84 11. Element (Elemento)
¿Queremos el primer elemento de la secuencia? Fácil:
string csharp = lenguajes.First();
Como es obvio, la salida es:
C#
11.3.3 First + condición
public static TSource First<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Ésta sobrecarga toma un delegado el cual usaremos como condición. ¿Condición para qué? Para extraer el primer elemento que cumpla dicha condición. Por ejemplo, queremos el primer elemento de la secuencia que tenga un solo carácter. ¿No iba a ser siempre el primer elemento y ya está, no?:
string unChar = lenguajes.First(e => e.Length == 1);
¿Cuál es el primer elemento de la secuencia con un solo carácter?:
C
11.4 FirstOrDefault
El operador FirstOrDefault devuelve el primer elemento de una secuencia o un valor predeterminado si no encuentra ningún elemento.
11.4.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
Una lista vacía:
List<int> listaVacia = new List<int>();
85 Operadores de LINQ
11.4.2 FirstOrDefault estándar
public static TSource FirstOrDefault<TSource>( this IEnumerable<TSource> source )
FirstOrDefault devuelve el primer elemento de una secuencia o un valor predefinido si no hay elementos. Por ejemplo, queremos el primer elemento de la lista vacía:
int primero = listaVacia.FirstOrDefault();
Al estar vacía, devuelve un valor predefinido. ¿Cuál es el valor por defecto de los int?:
0
11.4.3 FirstOrDefault + condición
public static TSource FirstOrDefault<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Ahora tenemos la posibilidad de elegir el primer elemento de una secuencia que cumpla una cierta condición. Si ningún elemento cumple dicha condición, se devolverá un valor predefinido. Queremos el primer lenguaje de la lista con 5 caracteres:
string cinco = lenguajes.FirstOrDefault(e => e.Length == 5);
Al no existir ninguno, se devuelve el valor por defecto de los string, o sea, null
11.5 Last
El operador Last devuelve el último elemento de una secuencia.
11.5.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
86 11. Element (Elemento)
11.5.2 Last estándar
public static TSource Last<TSource>( this IEnumerable<TSource> source )
Last no toma ningún parámetro. Simplemente ejecutarlo en la secuencia en la que queramos extraer el último elemento. ¿Queremos el último elemento de la secuencia? Fácil:
string c = lenguajes.Last();
Como es obvio, la salida es:
C
11.5.3 Last + condición
public static TSource Last<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Ésta sobrecarga toma un delegado el cual usaremos como condición. ¿Condición para qué? Para extraer el último elemento que cumpla dicha condición. Por ejemplo, queremos el último elemento de la secuencia que tenga 3 o más caracteres.
string tresChar = lenguajes.Last(e => e.Length >= 3);
¿Cuál es el último elemento de la secuencia con 3 o más caracteres?:
PHP
87 Operadores de LINQ
11.6 LastOrDefault
El operador LastOrDefault devuelve el último elemento de una secuencia o un valor predeterminado si no encuentra ningún elemento.
11.6.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
Una lista vacía:
List<int> listaVacia = new List<int>();
11.6.2 LastOrDefault estándar
public static TSource LastOrDefault<TSource>( this IEnumerable<TSource> source )
LastOrDefault devuelve el último elemento de una secuencia o un valor predefinido si no hay elementos. Por ejemplo, queremos el último elemento de la lista vacía:
int ultimo = listaVacia.LastOrDefault();
Al estar vacía, devuelve un valor predefinido. ¿Cuál es el valor por defecto de los int?:
0
11.6.3 LastOrDefault + condición
public static TSource LastOrDefault<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Ahora tenemos la posibilidad de elegir el último elemento de una secuencia que cumpla una cierta condición. Si ningún elemento cumple dicha condición, se devolverá un valor predefinido.
88 11. Element (Elemento)
Queremos el último lenguaje de la lista que empiece por Z
string zeta = lenguajes.LastOrDefault(e => e.StartsWith("Z"));
Al no existir ninguno, se devuelve el valor por defecto de los string, o sea, null.
11.7 Single
El operador Single devuelve el único elemento de una secuencia. Si hay más de un elemento dará una excepción. Si no hay elementos también dará excepción.
11.7.1 Código necesario para los ejemplos
Una lista con un solo elemento:
List<int> unEle = new List<int> { 24 };
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
11.7.2 Single estándar
public static TSource Single<TSource>( this IEnumerable<TSource> source )
Este operador se usa sobre una secuencia que contenga un solo elemento. Por ejemplo:
int numero = unEle.Single();
En este caso nada raro, devuelve:
24
Lo que tiene este operador es que si la lista contiene más de un elemento (o ninguno), dará una excepción.
89 Operadores de LINQ
11.7.3 Single + condición
public static TSource Single<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Ahora podemos establecer una condición. Devolverá el elemento que cumpla dicha condición. ¡Pero atención! Si hay más de un elemento que cumpla dicha condición dará una excepción. También la dará si ninguno la cumple. Queremos el lenguaje de 2 caracteres:
string csharp = lenguajes.Single(e => e.Length == 2);
Como solo existe uno, no tira ninguna excepción:
C#
11.8 SingleOrDefault
El operador SingleOrDefault devuelve el único elemento de una secuencia o un valor predeterminado si está vacía. Da una excepción si hay más de un elemento.
11.8.1 Código necesario para los ejemplos
Una lista de lenguajes:
List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
Una lista vacía:
List<int> listaVacia = new List<int>();
11.8.2 SingleOrDefault estándar:</u></b>
public static TSource SingleOrDefault<TSource>( this IEnumerable<TSource> source )
Este operador se usa sobre una secuencia. Si esta tiene un elemento devolverá dicho elemento. En caso de que esté vacía, devolverá un valor predefinido. Si tiene más de un elemento lanzará una excepción.
90 11. Element (Elemento)
Por ejemplo, queremos el valor de la lista vacía:
int vacia = listaVacia.SingleOrDefault();
Como no existe tal elemento, devolverá:
0
O sea, el valor por defecto de int.
11.8.3 SingleOrDefault + condición
public static TSource SingleOrDefault<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Este operador se usa sobre una secuencia. Devolverá el elemento que cumpla la condición especificada. En caso de no encontrar ninguno devolverá un valor predefinido. Si tiene más de un elemento que cumpla dicha condición, lanzará una excepción. Queremos el elemento de 4 caracteres:
string cuatroChar = lenguajes.SingleOrDefault(e => e.Length == 4);
¡Ah! Que no hay... Bueno, entonces devolverá null que es el valor predefinido de los string.
91 Operadores de LINQ
12. Generation (Generación)
12.1 DefaultIfEmpty
El operador DefaultIfEmpty devuelve una secuencia o un valor predefinido si dicha secuencia está vacía.
12.1.1 Código necesario para los ejemplos
Una lista de lenguajes: List<string> lenguajes = new List<string> { "C#", "Visual Basic", "Python", "PHP", "C" };
Una lista vacía:
List<int> listaVacia = new List<int>();
12.1.2 DefaultIfEmpty estándar
public static IEnumerable<TSource> DefaultIfEmpty<TSource>( this IEnumerable<TSource> source )
El operador se usa sobre una secuencia. Si la secuencia contiene elementos pues devolverá dichos elementos sin tocar. Si no hay elementos, devolverá el valor predefinido. Por ejemplo, si lo usamos en la lista de lenguajes:
IEnumerable<string> llena = lenguajes.DefaultIfEmpty();
Al no estar vacía, simplemente devolverá todos y cada uno de los elementos:
C# Visual Basic Python PHP C
¿Pero qué pasa si la usamos en una vacía?:
IEnumerable<int> vacia = listaVacia.DefaultIfEmpty();
92 12. Generation (Generación)
Que al no haber elementos, devolverá el valor predefinido, o sea:
0
12.1.3 DefaultIfEmpty + valor predefinido
public static IEnumerable<TSource> DefaultIfEmpty<TSource>( this IEnumerable<TSource> source, TSource defaultValue )
¿Podríamos cambiar el valor predefinido por uno más útil para nuestra aplicación? Con esta sobrecarga podemos definir cuál será dicho valor. Por ejemplo, en nuestra aplicación usamos el número -1 para indicar que una lista está vacía (Siempre es mejor usar una enumeración en estos casos):
IEnumerable<int> vacia = listaVacia.DefaultIfEmpty(-1);
Simplemente tenemos que pasarle dicho valor predefinido al operador y lo usará si la secuencia está vacía. Obviamente el contenido de vacia es:
-1
12.2 Empty
El operador Empty crea una enumeración de un tipo dado.
12.2.1 Empty
public static IEnumerable<TResult> Empty<TResult>()
Éste operador no se usa en ninguna secuencia, se usa para crear una enumeración de un tipo dado por el operador. Por ejemplo, si queremos una enumeración de cadenas podemos hacer:
IEnumerable<string> cadenas = Enumerable.Empty<string>();
Con eso tenemos una enumeración de string lista para ser usada con algún otro operador.
93 Operadores de LINQ
12.3 Range
El operador Range genera una secuencia de números enteros.
12.3.1 Range
public static IEnumerable<int> Range( int start, int count )
Al operador le pasamos dos números. El de inicio y el que indica la cantidad de números a generar. Por ejemplo si queremos generar 5 números haríamos:
IEnumerable<int> numeros = Enumerable.Range(1, 5);
Le estamos diciendo que empiece por 1 y que genere cinco números, entonces la salida sería:
1 2 3 4 5
Hay que fijarse que genera los números en orden, si quisiéramos por ejemplo los siguientes 5 números pares empezando por el 2 necesitaríamos la ayuda de otro operador, por ejemplo:
IEnumerable<int> numeros = Enumerable.Range(2, 5) .Select(x => x * 2);
Cada número lo multiplicamos por 2 y ya está :P
4 6 8 10 12
94 12. Generation (Generación)
12.4 Repeat
El operador Repeat genera una secuencia de un elemento repetido x veces.
12.4.1 Repeat
public static IEnumerable<TResult> Repeat<TResult>( TResult element, int count )
Al operador le indicamos el elemento a repetir y la cantidad de veces. Por ejemplo, vamos a crear una enumeración que contenta la cadena El Blog de Fox 5 veces:
IEnumerable<string> blogFox = Enumerable.Repeat("El Blog de Fox", 5);
O sea:
El Blog de Fox El Blog de Fox El Blog de Fox El Blog de Fox El Blog de Fox
95 Operadores de LINQ
13. Quantifier (Cuantificadores)
13.1 All
El operador All comprueba si todos los elementos de una secuencia cumplen una condición.
13.1.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Maria", "Julia", "Carla" };
13.1.2 All
public static bool All<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Como parámetro recibe un delegado que será la condición que ha de cumplir (o no) los elementos de una secuencia. Devuelve un bool. Por ejemplo, ¿Todos los nombres de la lista tienen 5 caracteres?:
bool cinco = nombres.All(n => n.Length == 5);
¿Verdad que si? Claro que si:
True ¿Pero terminan todos los nombres por a
bool a = nombres.All(n => n.EndsWith("a"));
Casi, así que:
False
96 13. Quantifier (Cuantificadores)
13.2 Any
El operador Any comprueba si una secuencia contiene elementos ya sean de cualquier tipo o con una condición.
13.2.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Maria", "Julia", "Carla" };
Una lista vacía:
List<int> listaVacia = new List<int>();
13.2.2 Any estándar
public static bool Any<TSource>( this IEnumerable<TSource> source )
El operador se usa en una secuencia para determinar si contiene elementos o no. Devuelve un bool. Por ejemplo, con la lista de nombres:
bool any = nombres.Any();
Sí que hay nombres:
True
Pero hay algo en la lista de números:
bool any = listaVacia.Any();
No, no hay nada:
False
97 Operadores de LINQ
13.2.3 Any + condición
public static bool Any<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
En esta sobrecarga comprobaremos si existe algún elemento que cumpla la condición que pasaremos como parámetro al operador. Solo hace falta que uno cumpla la condición. ¿Algún nombre que termine en s?:
bool any = nombres.Any(n => n.EndsWith("s"));
Sí que hay sí:
True
¿Alguno de 6 caracteres?:
bool any = nombres.Any(n => n.Length == 6);
No, todos de 5:
False
13.3 Contains
El operador Contains comprueba si una secuencia contiene un elemento especificado.
13.3.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Maria", "Julia", "Carla" };
98 13. Quantifier (Cuantificadores)
Un comparador de iniciales:
public class ComparadorInicial : IEqualityComparer<string> { public bool Equals(string x, string y) { return x[0] == y[0]; } public int GetHashCode(string obj) { return obj[0].GetHashCode(); } }
13.3.2 Contains estándar
public static bool Contains<TSource>( this IEnumerable<TSource> source, TSource value )
Le pasamos como parámetro el valor que queremos ver si existe en la secuencia. Devuelve un bool. ¿Habrá alguien con el nombre Julia en nuestra lista de nombres?:
bool julia = nombres.Contains("Julia");
Sí, sin duda:
True
¿Y algún Álvaro?:
bool alvaro = nombres.Contains("Alvaro");
No, esta vez no:
False
99 Operadores de LINQ
13.3.3 Contains + comparador personalizado
public static bool Contains<TSource>( this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer )
Ahora tenemos la posibilidad de usar nuestro propio comparador personalizado. ¿Algún nombre que empiece por C?:
bool c = nombres.Contains("C", new ComparadorInicial());
Sí, está Carla:
True
¿Alguno que empiece por H?:
bool h = nombres.Contains("H", new ComparadorInicial());
No.
False
100 14. Aggregation (Agregación)
14. Aggregation (Agregación)
14.1 Aggregate
El operador Aggregate va acumulando los elementos de una secuencia aplicando una función.
14.1.1 Código necesario para los ejemplos
Una lista de números:
List<int> numeros = new List<int> { 5, 2, 20, 5, 4, 4 };
14.1.2 Aggregate estándar
public static TSource Aggregate<TSource>( this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func )
Este operador es bien complicado de entender al principio, voy a poner el ejemplo y explicarlo con él:
int suma = numeros.Aggregate((an, pos) => an + pos);
Como veis, el operador toma como parámetro un delegado el cual recibe 2 valores y devuelve otro. El primer elemento que recibe es el resultado de dicho delegado en la iteración anterior y el segundo es el siguiente elemento. En la primera iteración al no haber ningún resultado todavía, el primer valor es el primer elemento y el segundo valor es el segundo elemento. Como esto es difícil de asimilar, vamos a hacer una especie de "debugging": Primera iteración: an -> 5 pos -> 2 Segunda iteración: an -> 7 pos -> 20
101 Operadores de LINQ
Tercera iteración: an -> 27 pos -> 5 Y así hasta terminar con todos los elementos. Como se puede ver, empieza tomando los 2 primeros valores, los suma (que es lo que hace nuestro delegado) y devuelve la suma. Esa suma será el primer valor del delegado y el segundo será un nuevo elemento de la secuencia. Los volverá a sumar y así hasta que termina. En otras palabras, va acumulando los resultados hasta llegar al final. El resultado final es:
40
14.1.3 Aggregate + valor de inicio
public static TAccumulate Aggregate<TSource, TAccumulate>( this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func )
Esta sobrecarga recibe un valor de inicio. Como ya sabes, el operador Aggregate va iterando por cada elemento y acumulando los resultados. Al principio no tiene nada acumulado, pero con esta sobrecarga podemos especificar un valor de inicio. Por ejemplo, queremos a volver a sumar los números, pero ahora en vez de empezar con el acumulador a 0, lo iniciamos a 20:
int suma = numeros.Aggregate(20, (an, pos) => an + pos);
Entonces en la primera iteración sería: an -> 20 pos -> 5 Entonces el resultado sería:
60
102 14. Aggregation (Agregación)
14.1.4 Aggregate + valor de inicio + funcion elemento resultante
public static TResult Aggregate<TSource, TAccumulate, TResult>( this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector )
Además del valor de inicio, ahora tenemos la posibilidad de aplicarle una función al resultado final, o sea, al valor acumulado que hemos conseguido al terminar todas las iteraciones. Por ejemplo, vamos a volver a sumar los números, empezaremos por 20 y cuando terminemos lo vamos a dividir por 2:
int suma = numeros.Aggregate(20, (an, pos) => an + pos, res => res / 2);
¿Ya no nos cuesta entenderlo verdad? Empezamos a sumar con el contador a 20, y terminamos con el valor 60. Ese valor lo dividimos entre 2 así que el resultado es:
30
14.2 Average
Con el operador Average podemos calcular la media de una secuencia de valores numéricos.
14.2.1 Código necesario para los ejemplos
Una lista de números enteros:
ist<int> numeros = new List<int> { 5, 2, 20, 5, 4, 4 };
Una lista de números en coma flotante con un valor nulo:
List<double?> numeros2 = new List<double?> { null, 42, 5.4, 3.2, 2, 4 };
103 Operadores de LINQ
14.2.2 Average estándar
public static TResult Average<TResult>( this IEnumerable<TResult> source )
El operador Average tiene múltiples sobrecargas, pero todas hacen lo mismo solo que cada una trabaja con un tipo distinto. La lista de tipos con la que trabaja average es:
Decimal Decimal? Double Double? Int32 Int32? Int64 Int64? Single Single?
¿Cuál es la media de la primera secuencia de números?:
double media = numeros.Average();
El resultado es:
6,67
¿Y cuál es la media de la segunda secuencia de números que además tiene un valor nulo?:
double? media = numeros2.Average();
Es:
11,32
104 14. Aggregation (Agregación)
14.2.3 Average + función de transformación
En esta sobrecarga podemos aplicar una función de transformación a cada elemento antes de hacer la media. Vamos a multiplicar por dos cada número de la secuencia de enteros y hacer la media luego:
double media = numeros.Average(n => n * 2);
El resultado es:
13,33
14.3 Count
El operador Count cuenta el número de elementos de una secuencia.
14.3.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Maria", "Julia", "Alvaro", "Manolo" };
14.3.2 Count estándar
public static int Count<TSource>( this IEnumerable<TSource> source )
Usamos Count en una secuencia en la que queremos contar sus elementos. Devuelve un int con el número de elementos de dicha secuencia. Por ejemplo, ¿Cuántos nombres tenemos?
int count = nombres.Count();
La respuesta es:
5
105 Operadores de LINQ
14.3.3 Count + condición
public static int Count<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Si lo que realmente queremos es contar la cantidad de elementos de una secuencia que cumplen cierta condición, podemos ahora usar un delegado que usaremos como dicha condición. ¿Cuántos nombres terminan en o?
int count = nombres.Count(n => n.EndsWith("o"));
La respuesta es:
2
14.4 LongCount
El operador LongCount cuenta el número de elementos de una secuencia. A diferencia de Count, LongCount devuelve un long y no un int.
14.4.1 Código necesario para los ejemplos
Una lista de nombres:
List<string> nombres = new List<string> { "Jesus", "Maria", Julia", "Alvaro", "Manolo" };
14.4.2 LongCount estándar
public static long LongCount<TSource>( this IEnumerable<TSource> source )
Usamos LongCount en una secuencia en la que queremos contar sus elementos. Devuelve un long con el número de elementos de dicha secuencia. Por ejemplo, ¿Cuántos nombres tenemos?
long count = nombres.LongCount();
106 14. Aggregation (Agregación)
La respuesta es:
5
14.4.3 LongCount + condición
public static long LongCount<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
Si lo que realmente queremos es contar la cantidad de elementos de una secuencia que cumplen cierta condición, podemos ahora usar un delegado que usaremos como dicha condición. ¿Cuántos nombres empiezan por J?
long count = nombres.LongCount(n => n.StartsWith("J"));
La respuesta es:
2
14.5 Max
El operador Max devuelve el valor máximo de una secuencia de valores.
14.5.1 Código necesario para los ejemplos
Una lista de números enteros:
List<int> numeros = new List<int> { 5, 2, 20, 5, 4, 4 };
14.5.2 Max estándar
public static TSource Max<TSource>( this IEnumerable<TSource> source )
El operador Max tiene múltiples sobrecargas, pero todas hacen lo mismo solo que cada una trabaja con un tipo distinto. La lista de tipos con la que trabaja Max es:
Decimal Decimal? Double
107 Operadores de LINQ
Double? Int32 Int32? Int64 Int64? Single Single?
¿Cuál es el valor máximo de nuestra lista de números?:
int maximo = numeros.Max();
Usamos la sobrecarga correspondiente para el tipo de datos correspondiente de la secuencia. El resultado es:
20
14.5.3 Max + función de transformación
public static TResult Max<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector )
Ésta sobrecarga aplicará una función a cada elemento antes de buscar el valor máximo de la secuencia. Vamos a calcular el cuadrado de cada número y luego ver el mayor:
int maximo = numeros.Max(n => n * n);
El resultado es:
400
108 14. Aggregation (Agregación)
14.6 Min
El operador Min devuelve el valor mínimo de una secuencia de valores.
14.6.1 Código necesario para los ejemplos
Una lista de números enteros:
List<int> numeros = new List<int> { 5, 2, 20, 5, 4, 4 };
14.6.2 Min estándar
public static TSource Min<TSource>( this IEnumerable<TSource> source )
El operador Min tiene múltiples sobrecargas, pero todas hacen lo mismo solo que cada una trabaja con un tipo distinto. La lista de tipos con la que trabaja Min es:
Decimal Decimal? Double Double? Int32 Int32? Int64 Int64? Single Single?
¿Cuál es el valor mínimo de nuestra lista de números?:
int minimo = numeros.Min();
Usamos la sobrecarga correspondiente para el tipo de datos correspondiente de la secuencia. El resultado es:
2
109 Operadores de LINQ
14.6.3 Min + función de transformación
public static TResult Min<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, TResult> selector )
Ésta sobrecarga aplicará una función a cada elemento antes de buscar el valor mínimo de la secuencia. Vamos a multiplicar cada número por cinco y calcular el mínimo:
int minimo = numeros.Min(n => n * 5);
El resultado es:
10
14.7 Sum
El operador Sum devuelve la suma de los valores de una secuencia.
14.7.1 Código necesario para los ejemplos
Una lista de números enteros:
List<int> numeros = new List<int> { 5, 2, 20, 5, 4, 4 };
14.7.2 Sum estándar
El operador Sum tiene múltiples sobrecargas, pero todas hacen lo mismo solo que cada una trabaja con un tipo distinto. La lista de tipos con la que trabaja Sum es:
Decimal Decimal? Double Double? Int32 Int32? Int64 Int64? Single Single?
110 14. Aggregation (Agregación)
¿Cuál es la suma de los números de la secuencia?
int suma = numeros.Sum();
¿Recuerdas que hicimos esto mismo usando Average? Obviamente esta es la forma correcta de hacer este tipo de operación. El resultado es:
40
14.7.3 Sum + función de transformación
Ésta sobrecarga aplicará una función a cada elemento antes de buscar el valor mínimo de la secuencia. Vamos a restarle tres a cada número y luego hacer la suma:
int suma = numeros.Sum(n => n - 3);
El resultado es:
22