Sentencia Básicas
La forma más sencilla de realizar
una consulta sobre LINQ es obtener una lista del elemento que queremos
recuperar. Así, si quisiéramos obtener una lista de Clientes, la consulta
adecuada para ello sería la siguiente:
1 2 |
var listaClientes = from c in DataLists.ListaClientes select c; |
La cláusula from de
LINQ es más fácil de entender si la interpretamos como si fuera un foreach.
De hecho, el siguiente fragmentos de código realizaría más o menos la misma
tarea: declarar una variable de tipo Cliente e iterar sobre la
lista DataLists.ListaClientes:
1 2 3 4 5 6 7 8 |
// Declaramos una lista de
clientes IEnumerable<Cliente>
listaClientes = new List<Cliente>(); // Declaramos una variable de
rango "c" que iterará sobre DataLists.ListaClientes foreach( Cliente c in DataLists.ListaClientes) // <<-- Equivaldrá a
“from c in DataLists.ListaClientes” { ((List<Cliente>)listaClientes).Add(c);
// <<-- Equivaldrá a “select c”; } |
Por lo tanto, la razón por la que
el from va antes del select es poder declarar la variable
de rango c y tener la posibilidad de hacer uso de ella en posteriores
cláusulas, como select, orderby…
Obtener un campo concreto del objeto
Como acabamos de decir, la
sentencia select permite hacer uso de la variable de rango definida
en la sentencia from. Por lo tanto, ¿sería posible, en lugar de proyectar
un objeto completo, hacer que nuestra lista se componga de los elementos
contenidos en un campo específico en lugar de hacerlo con el objeto entero?
Definitivamente, sí. Aquí está el ejemplo:
1 2 |
var listaNombresClientes = from c in DataLists.ListaClientes select c.Nombre; |
Si analizamos el resultado, veremos
que no estamos recorriendo un listado de objetos Cliente, sino que la
lista contiene simples cadenas de texto, correspondientes
Obtener varios campos del objeto: tipos
anónimos
Quizás alguien se esté
preguntando ahora: ¿y si quiero obtener más de un campo, digamos el nombre del
cliente y su fecha de nacimiento? ¿Podría hacer algo como lo siguiente?:
1 2 |
var listaNombreFechaClientes = from c in DataLists.ListaClientes select c.Nombre, c.FechaNac; |
Desgraciadamente, no. LINQ se
parece a SQL, pero no es SQL. Si queremos recuperar más de un elemento
deberemos hacer uso bien de una clase o estructura que declaremos
explícitamente para ello (por ejemplo, creando un nuevo Cliente), bien haciendo
uso de un tipo anónimo.
El Framework 3.0 relativiza la
necesidad de codificar un conjunto de constructores, cada cual con unos
parámetros distintos, permitiendo crear un único constructor que aglutine las
tareas básicas de inicialización y dejando al programador que añada manualmente
las propiedades que estime oportunas a la hora de instanciar el objeto. Así, si
quisiéramos devolver los campos «Nombre» y «FechaNac» de la clase «Cliente»,
podríamos hacer lo siguiente:
1 2 |
var listaClientesIncompletos = from c in DataLists.ListaClientes select new Cliente {
Nombre = c.Nombre, FechaNac = c.FechaNac }; |
Fabuloso, ¿verdad? Hemos creado
un nuevo objeto Cliente y le hemos asignado valor a sus
propiedades Nombre y FechaNac de forma dinámica sin necesidad
de un constructor específico que espere esos parámetros. Podemos realizar esta
operación con cualquier objeto que disponga de propiedades públicas.
Sin embargo, esto acarrea un
problema. Echemos un vistazo al contenido de uno de los objetos pertenecientes
a la lista devuelta por la sentencia LINQ:
Hemos instanciado un nuevo objeto
de la clase Cliente, pero hacer esto implica que los tipos básicos se
inicialicen con su valor por defecto. En este caso, la propiedad entera Id se
inicializa con el valor 0, cuando obviamente sabemos que este valor no se
corresponde con el Id real de ese campo en nuestra «base de datos». Quizás en
nuestro código seamos perfectamente conscientes de este hecho, pero ¿lo sabrá
la persona que, en un futuro, tenga que mantener este código?
Descartando utilizar un objeto
completo para obtener tan sólo un puñado de campos, nos quedaría la opción de
crear una clase específica para alojar estos dos campos. Sin embargo, esta
operación puede darse demasiada frecuencia como para tener que crear una clase
específica para todas las posibles combinaciones de campos de una lista que
queramos recuperar. Esta tarea podría resultar demasiado tediosa. Por ello, el
framework nos ofrece un artefacto que solucionará de un plumazo todos los problemas
relacionados con esta pequeña disyuntiva: los tipos anónimos.
Un tipo anónimo no es más que un
tipo generado de forma dinámica. Fin. No hay más misterio.
Al igual que hacíamos con el
«no-constructor» de la clase Cliente pasándole el nombre de las propiedades
junto a sus valores a la hora de crearlo, podemos hacer la misma
operación sin necesidad de que la clase exista. Hablando en plata, nos
inventamos la clase por el camino, y el compilador, a partir del punto en el
que hayamos declarado el nuevo tipo, nos permitirá acceder a sus propiedades.
Veámoslo mejor con un ejemplo:
1 2 3 4 5 6 |
// Declaramos (y cumplimentamos)
el tipo anónimo var tipoAnonimo = new { PropiedadEntera = 1,
PropiedadCadena = "cadena" }; // Mostramos por pantalla su contenido Console.WriteLine(String.Format("El
contenido del tipo anónimo es {0} y {1}", tipoAnonimo.PropiedadEntera,
tipoAnonimo.PropiedadCadena)); |
La primera línea crearía un tipo
anónimo (nótese que lo declaramos como var) con dos atributos (que
se tipan en tiempo de compilación a int y string respectivamente,
a partir de los tipos asignados):
·
AnonymousType tipoAnonimo
·
PropiedadEntera (int)
·
PropiedadCadena (string)
De nuevo nos enfrentaremos a la
disyuntiva del uso de var en nuestro código: perderemos legibilidad a
cambio de ganar versatilidad. Si se pidiera mi opinión, en este caso yo estaría
dispuesto a realizar el sacrificio. Por lo tanto, si incluimos un tipo anónimo
en la cláusula select de una sentencia LINQ, podríamos hacer lo
siguiente:
1 2 |
var listaNombreFechaClientes = from c in DataLists.ListaClientes select new {
NombreCliente = c.Nombre, FechaNacimiento = c.FechaNac}; |
Si observamos el contenido del
objeto con una inspección, veremos que el listado devuelto será una lista
de Anonymous Type compuesto por los campos NombreCliente (string)
y FechaNacimiento (DateTime), propiedades que nos acabamos de
inventar pertenecientes, también, a un tipo que nos acabamos de inventar.
Si el lector es astuto, puede que
piense en un nuevo problema potencial. Hasta ahora, todo muy bonito y muy
potente, pero… si el tipo es anónimo y no podemos declarar una clase de su
tipo, ¿cómo narices accedemos a sus elementos?
La respuesta puede sonar brusca:
«a pelo». Que el tipo sea anónimo no implica que la variable que utilicemos
para almacenar los datos no esté fuertemente tipada. Lo único que estamos
haciendo es delegar en el compilador la tarea de asignar el tipo, pero una vez
que el compilador conoce el tipo del objeto, nos permitirá usarlo sin
provocar errores de compilación. Así, podríamos usar el siguiente bucle para
acceder a los elementos de la lista devuelta por la sentencia LINQ:
1 2 3 |
foreach(var nombreFecha in listaNombreFechaClientes) Console.WriteLine(string.Format("El
cliente {0} nació el {1}", nombreFecha.NombreCliente,
nombreFecha.FechaNacimiento)); |
El resultado de esta llamada
sería el siguiente:
De nuevo, el avispado lector
probablemente se haya dado cuenta de otro potencial problema a la hora de
utilizar tipos anónimos: ¿y si, en lugar de utilizar la variable que me ha
devuelto la sentencia LINQ, quisiera pasar ese listado como parámetro a
una función. ¿Cómo nos las arreglamos? Aquí el framework se rinde y pide el
cambio, ya que un tipado fuerte es incompatible con esta funcionalidad. Por lo
tanto, podemos decir que
1. Sí,
es posible pasar como parámetro un tipo anónimo a una función o método.
2. A
partir de este punto, el framework no se responsabiliza de los posibles daños
ocasionados por tal osadía.
Lo que queremos decir es que
mientras nos movamos dentro de un único ámbito o scope (por ejemplo,
dentro de una única función), el framework será capaz de inferir el tipo de
cada uno de los elementos anónimos que tengamos a bien generar. Sin embargo, en
el momento en el que creemos una función y ésta reciba un parámetro de tipo
anónimo, el framework se lavará las manos. El compilador, en un alarde de magnanimidad,
nos permitirá que invoquemos a tantos métodos y propiedades del tipo anónimo,
pero dejando a nuestra responsabilidad el hecho de que los elementos invocados
existan y que sus parámetros y formatos sean correctos. Entramos en el terreno
del tipado débil.
Por lo tanto, si quisiéramos
generar una función que reciba una lista de elementos anónimos y realizara la
misma operación que vimos más arriba, bastaría con lo siguiente:
1 2 3 4 5 6 |
private static void mostrarResultados(IEnumerable<dynamic>
listado) { foreach (var nombreFecha
in listado) Console.WriteLine(string.Format("El
cliente {0} nació el {1}", nombreFecha.NombreCliente,
nombreFecha.FechaNacimiento)); } |
Vemos que el parámetro es de
tipo IEnumerable<dynamic>. En realidad bastaría con pasar un
parámetro de tipo dynamic, que se correspondería con el tipo
anónimo en sí, pero dado que sabemos que vamos a recibir una colección de
tipos anónimos, seamos amables e indiquémosle a nuestro compilador esta información,
ahorrándole unos cuantos ciclos de CPU a la máquina que realizará la
compilación y algún que otro dolor de cabeza al colega que tendrá que
encargarse en un futuro de mantener nuestra chapuza nuestro código.
Si desde la función original
invocamos el método pasándole la consulta generada por LINQ en el ejemplo
anterior, todo funcionará como la seda, ya que los nombres y tipos de las
propiedades de nuestro tipo anónimo (NombreCliente (string) y FechaNacimiento
(DateTime)) coinciden por las que espera nuestro método.
Pero… ¿y si no es así? ¿Y si en
lugar de recibir un campo NombreCliente recibimos un campo
llamado Nombre a secas? ¿Qué ocurriría? La respuesta es fácil de
imaginar: Pum.
Por lo tanto, tal y como
aconsejan las autoridades sanitarias con el alcohol, podemos decir lo mismo con
los tipos anónimos: sí, pero consúmalos con responsabilidad. No permita que un
tercero se vea perjudicado por no tomar las medidas oportunas.
Más adelante seguiremos con LINQ,
mostrando cómo podemos aplicar filtros, ordenaciones