Dentro del mundo de LinQ,
las proyecciones, determinan el tipo de datos que será devuelto o proyectado
cuando sea aplicado a uno de nuestras secuencias IEnumerable<T>.
Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.
Select
El operador Select
se utiliza para crear una secuencia
de salida de un tipo de datos a partir de una secuencia de entrada de otro tipo de elementos. Los tipos de
entrada y salida no tienen por qué ser del mismo tipo de datos. En definitiva
el operador Select transforma
unos datos de entrada en unos datos de salida.
Vamos a ver un ejemplo muy sencillo, para ir después
desgranando toda la chicha que lleva dentro. Para ello utilizaremos una clase
de mi tipo preferido para hacer ejemplos, la clase Persona:
Y aquí el
ejemplo:
Resultado:
Como vemos en este ejemplo tan sencillo, tenemos un List<Persona> como secuencia de entrada y mediante el uso del operador Select la transformamos en un IEnumerable<string> como secuencia de salida.
Dentro de la comparativa inevitable entre LinQ y SQL, hay una práctica muy utilizada dentro del mundo de las
bases de datos, que es la selección de un número determinado de campos de una
tabla, normalmente siempre inferior a la totalidad de los campos de la misma,
una consulta de este tipo:
Algo completamente común y extremadamente sencillo en SQL. Esto también lo podemos hacer
en LinQ, y aquí es donde entran en
juego la utilización de los tipos
anónimos y la asignación
implícita de tipos, que ya estudiamos en entradas anteriores. En SQL, esto lo hace de manera
trasparente el motor de base de datos.
Vamos a ver ese ejemplo en LinQ:
Dando como resultado:
Como podemos comprobar el tipo de datos de resultado es <>F__AnonymousType0`2 que es el nombre del tipo de datos que le asigna el compilador de manera automática, ahorrándonos tener que crear uno para la causa.
A modo de resumen,
comentar que si dentro de nuestro código, necesitáramos hacer algo más con esta
colección de resultado de tipo anónimo,
como pasarlo por parámetros a un método consumidor o devolverlo como return de una función, no nos quedaría más remedio que crear un tipo ‘conocido’ de resultado, como por
ejemplo:
Y dentro de la cláusula o el método extensor Select, cambiaríamos así las
llamadas:
También podemos darle otro uso alejado de la analogía con SQL y utilizarlo como transformador
de datos dentro de lo que podría ser una de nuestras tareas del día a día, como
en este caso:
Simplemente listaremos los ficheros de la unidad C: y crearemos FileInfos a partir de su ruta, para tener acceso a todos sus
atributos, con el siguiente resultado:
Y para finalizar, vamos a ver cómo podríamos hacer nuestro propio método extensor Select y lo fácil que sería hacer una implementación de éste:
Si tenéis algún tipo de duda sobre la sintaxis de los
métodos extensores, os dejo aquí
un enlace donde podréis ver la entrada anterior del blog y donde se explican
ampliamente.
Lo primero que vemos es que es un método genérico
que contiene 2 variables de tipo T
y S. T será el tipo de datos de entrada y S el tipo de datos de salida. Nuestro método atesora como
parámetro extensor source,
una secuencia de elementos de tipo T,
IEnumerable<T> que será
nuestra colección de entrada y devuelve una secuencia de datos de tipo S, IEnumerable<S> que será la secuencia de salida o
resultado. Solo nos queda hablar del parámetro selector que se trata de un delegado anónimo Func,
que precisará un objeto de tipo T
para dar como resultado un objeto de tipo S,
aquí será donde indicaremos la forma de realizar la conversión.
Método extensor (tener en cuenta que es una emulación y es
código creado de forma didáctica, el de Microsoft, es otra historia), aunque el
objetivo y el resultado sea completamente el mismo, eso sí, sin tener en cuenta
el rendimiento y el control claro está:
Y aquí consumiéndolo:
Es muy importante apreciar, que en este ejemplo solo lo he utilizado
Lambdas, y es que esta sobrecarga
carece de caso de uso con expresiones de consulta.
He utilizado nuestro método, pero podría haber hecho la
llamada al método de Microsoft con el mismo modo uso y el mismo resultado:
Resultado:
Considero bastante importante el valor didáctico de
construir nuestra propia implementación del método, ya que esto nos abre la
mente para un futuro, a la hora de necesitar de algún operador que no tengamos
disponible entre los que nos ofrece LinQ, siéndonos de gran ayuda en nuestro
objetivo de no repetir código.
SelectMany
El otro operador a tratar dentro de esta entrada será SelectMany. A mi parecer la palabra
que mejor describe el funcionamiento de este operador es DESAGRUPAR. Se encarga de proveernos una colección de datos de
salida, a partir de una fuente de datos formada, por lo que llamamos comúnmente
una colección de colecciones,
o por utilizar otro símil con BD, una relación de uno a n (1 – n).
Vamos a ver unos ejemplos, que irán subiendo de dificultad y
darán un poco de sentido a tanta palabra.
Nuestras clases bases serán las siguientes:
Ahora vamos a hacer un método que cargue Equipos:
Aquí tenemos nuestra colección de colecciones. Si pensamos
con nuestro celebro de desarrollador tradicional, esta sería la manera de
recorrer los resultados, mediante un bucle anidado:
El operador SelectMany,
nos permite desagruparlos de esta manera tan sencilla:
El resultado para ambos sería el mismo:
Este es el ejemplo más sencillo, y si vemos la firma de su
método extensor, es básicamente igual al del select:
La única diferencia está en el parámetro selector, que tiene
como tipo de retorno en vez de un objeto de tipo S, un objeto IEnumerable<S>.
Esto consta de todo el sentido del mundo ya que por cada uno de los objetos T que recibe, debe poseer una
propiedad que sea una colección, y que señalaremos para desagrupar, por lo que
el resultado será una colección de elementos y no un único elemento.
Viendo este ejemplo, supongo que a muchos se os pasará por
la cabeza, que ocurriría en caso de tener que imprimir algo referente al
Equipo, como por ejemplo el nombre del Club o la fecha de fundación, como en
este ejemplo:
Una de las sobrecargas de SelectMany, también nos permite tener acceso a los datos de los
clubes, de la siguiente forma:
En este segundo ejemplo, al igual que en el primero, se le
indica como primer parámetro la propiedad que tendrá la colección a desagrupar,
y una nueva lambda como segundo parámetro que nos facilita 2 argumentos, uno
para el Equipo y otro para cada uno de los jugadores, en el que tendremos que
construir el tipo de datos que tendrá cada uno de los elementos de nuestro
enumerador de resultado.
El resultado vendría a ser el mismo que si estuviéramos lanzando
una consulta Join a 2 tablas de
nuestra base de datos Equipos
y Jugadores, con una relación de 1 a n.
Más adelante, veremos el operador GroupJoin, que realizará exactamente el caso contrario al
operador SelectMany y que
desde la visión y el punto de vista del desarrollador, es la forma más óptima y
más clara de trabajar.
Resultado:
Vamos a ver cómo sería la firma de esta segunda sobrecarga:
Todo esto desglosado, quedaría así:
Tipos …
T à Es el tipo de datos del Objeto principal, el que tiene la
colección en nuestro caso Equipo.
K à Es el tipo de datos de cada
uno de los objetos secundarios o
de la colección o agrupación, en nuestro caso Jugador.
S à Es el tipo de datos de
resultado (puede ser un tipo conocido o un tipo
anónimo).
IEnumerable<S> à Es el resultado de la
función y devolverá una colección de objetos del tipo de resultado.
this IEnumerable<T> à Colección de objetos principales, que a su vez
contiene cada uno de ellos otra colección de objetos y que como indica la
palabra reservada this, será el
tipo que extenderemos.
Func<T, IEnumerable<S>>
à Este parámetro precisa
de objeto del tipo principal (Equipo)
y da como resultado una colección de objetos secundarios (Jugadores), es donde señalamos la propiedad de nuestro objeto
principal que tiene a su vez la colección de objetos secundarios.
Func<T, K, S> à En este último
parámetro, simplemente recibiremos, el objeto de tipo principal y el
secundario, para formar uno de tipo resultado.
Puedo parecer un poco pesado intentando desmigar este tipo
de firmas, pero entender todo esto, te hace ver el desarrollo en general de
otra forma y dar una potencia y dinamismo al lenguaje sin precedentes.
Aparte de estas 2 sobrecargas, el operador SelectMany, cuenta con 2 más, exactamente
iguales, pero añadiendo un índice, con el número de orden del registro dentro
de la colección, igual que veíamos en el operador Select o Where.
Excelente explicación!!!!
ResponderEliminarMuchas gracias por el aporte.
La mejor explicación que encontré en internet, muchas gracias y felicitaciones!!
ResponderEliminar