Empecemos viendo un ejemplo muy básico, que iremos ampliando para ver las distintas posibilidades que nos ofrece el lenguaje de consultas integrado en C#.
En el listado 6.7 definimos un array numérico y creamos un objeto que contiene una consulta LINQ. En ese código se seleccionan todos los valores numéricos del array que sean mayores de 4. La variable
res contiene la expresión de la consulta, es decir, contiene las instrucciones necesarias para realizar las operaciones que hemos indicado, en este caso, esas operaciones serán: todos los valores que haya en el array nums cuyo valor sea superior a 4.
var nums = new[] { 1, 9, 8, 2, 5, 7, 4, 3, 6 };
var res = from n in nums where n > 4 select n;
foreach(var v in res)
Console.WriteLine(v);
Listado 6.7. Una consulta LINQ con un array numérico
Cuando usamos esa variable en el bucle foreach es cuando se pone en marcha toda la maquinaria de LINQ para hacer efectivo ese código, dando como resultado una colección del tipo
IEnumerable<int> con los valores 9, 8, 5, 7 y 6 que son los que cumplen la condición indicada por la cláusula where.
Antes de entrar en detalles, analicemos la expresión de consulta y veamos lo que hace LINQ.
from n in nums, esto lo podemos leer como: asigna a la variable indicada después de from cada uno
de los valores de la colección indicada después de la cláusula in.
where n > 4, toma solo los elementos que coincidan con esta expresión.
select n, le indica al compilador qué elemento queremos incluir en el resultado de la consulta, en este caso un valor simple, el de cada número que cumpla la condición de la cláusula where.
La variable que recibe esa expresión de consulta es del tipo IEnumerable<T>, siendo T el tipo de- vuelto por select. En este ejemplo un tipo int. En esa colección estarán solo los valores que cumplan la condición indicada, es decir, los valores superiores a 4.
Ejecución aplazada y ejecución inmediata
Cuando el compilador se encuentra con esta expresión de consulta no ejecuta ese código de forma inmediata, simplemente prepara la variable (res en nuestro ejemplo del listado 6.7) para contener esa expresión, esto es lo que se conoce como consulta de ejecución aplazada (o ejecución diferida), es decir, cada vez que utilicemos esa variable, es cuando se ejecuta la consulta.
Esto significa, que si los elementos de la variable nums cambian, también cambiará el resultado final. Por ejemplo, el primer elemento del array es 1, si lo cambiamos a 10 y volvemos a ejecutar el bucle
foreach, veremos que también se muestra ese nuevo valor, ya que cumple la condición usada con
where. El listado 6.8 tiene ese cambio y en la figura 6.2 vemos el resultado de ejecutar los dos bucles.
nums[0] = 10;
Console.WriteLine("Después de hacer nums(0) = 10:");
foreach(var v in res)
Console.WriteLine(v);
Listado 6.8. Si cambiamos un valor del array y coincide con la condición, se incluirá en el resultado de la consulta
Figura 6.2. Resultado de ejecutar el código de los listados 6.7 y 6.8
Por tanto, cualquier cambio que hagamos a los valores del array se verá reflejado al utilizar la variable con la consulta. Al menos si el número de elementos de ese array no cambia, ya que si añadimos o quitamos elementos, la consulta usará los últimos valores que obtuvo antes del cambio de tamaño del
Esto último lo podemos comprobar añadiendo al código actual el mostrado en el listado 6.9, veremos que se muestra lo mismo que vemos al final de la figura 6.2.
// Quitamos un elemento del array
Array.Resize(ref nums, nums.Length - 1);
// Asignamos al primer elemento el valor 1
nums[0] = 1;
// Se mostrará lo que había antes del cambio
foreach(var v in res)
Console.WriteLine(v);
Listado 6.9. Si cambiamos el número de elementos del array usado en la consulta, se mantienen los últimos valores que hubiera en la consulta
La mayoría de las consultas LINQ son de tipo aplazada (se realizan cuando se utiliza la variable que contiene la consulta), aunque hay ciertas funciones que pueden obligar a que la consulta se haga de forma inmediata, por ejemplo, si usamos funciones agregadas o bien convertimos el resultado de la consulta en una lista (colección de tipo List<T>) o un array por medio de los métodos ToList o
ToArray respectivamente.
En el listado 6.10 tenemos estos dos casos comentados: en el primero, asignamos a la variable res2 el resultado de la suma de los elementos que devuelve la consulta, debido a que la única forma de efec- tuar esa suma es sabiendo los elementos que contiene, la consulta debe ejecutarse de forma inmediata; en el segundo, al convertir esa colección en una lista también debe ejecutarse la consulta y una vez creada la lista devuelta por el método ToList, cualquier cambio que hagamos al array no afectará al resultado. En realidad, ambas consultas se ejecutan porque usamos un método que trabaja con el re- sultado de la consulta, y por tanto, lo que se asigna a las variables es lo que se conoce como un valor
singleton, es decir, un valor directo en lugar de una expresión de consulta.
var res2 = (from n in nums where n > 4 select n).Sum();
Console.WriteLine(res2);
Console.WriteLine();
var res3 = (from n in nums where n > 4 select n).ToList();
foreach(var v in res3)
Console.WriteLine(v);
Console.WriteLine();
nums[0] = 5;
foreach(var v in res3)
Console.WriteLine(v);
Listado 6.10. Consultas de ejecución inmediata
La cláusula select
En todos estos ejemplos vemos cómo se utilizan las novedades que hemos estado examinando en capítulos anteriores. La principal de ellas es la inferencia de tipos, ya que el compilador infiere el tipo que deben tener las variables que reciben los resultados de las consultas, además de los tipos usados en la misma, por ejemplo, no es necesario indicar el tipo de la variable después de la cláusula from.
También estamos utilizando métodos de extensión, ya que tanto Sum como ToList son métodos que extienden la funcionalidad de la interfaz IEnumerable<T>. Y aunque no lo veamos en este ejemplo, la creación de tipos anónimos también será utilizada en la mayoría de las expresiones de consulta que creemos. En un momento veremos un ejemplo.
Normalmente en las expresiones de consultas, LINQ siempre se utiliza la cláusula select para indicar el valor que tendrá cada elemento de la colección creada. Habitualmente, después de select se indica la misma variable usada después de from, pero si queremos devolver valores que no sean ese valor simple, podemos indicar algo después de select. Ese “algo” será lo que se devuelva; por ejemplo, en
el listado 6.11, en lugar de devolver un valor entero, en cada iteración del bucle que cumpla con la condición indicada después de la cláusula where, devolvemos una cadena formada por el texto entre- comillado, además del valor del número analizado (y que cumple esa condición).
Y si queremos utilizar un tipo anónimo como elemento a añadir a la colección generada por la con- sulta, podemos hacerlo tal como vemos en el listado 6.12.
var res = from n in nums where n > 4
select "Número " + n;
Listado 6.11. Esta consulta devuelve los elementos como una cadena compuesta por el texto y el valor
var res2 = from n in nums where n > 4 select new { Valor = "Número " + n, EsPar = n % 2 == 0 };
foreach(var v in res2)
Console.WriteLine("{0}, es par = {1}", v.Valor, v.EsPar);
Listado 6.12. Una consulta que contiene elementos de tipo anónimo
En este código definimos un tipo anónimo con dos propiedades, la primera de tipo string y la segunda de tipo bool que nos indica con un valor true si el número en cuestión es par. En el bucle foreach
comprobamos que el tipo de datos se infiere a partir de ese tipo anónimo, ya que la variable usada en ese bucle implementa las dos propiedades definidas en la inicialización que hacemos después de se- lect. En la figura 6.3 podemos ver el resultado de ejecutar ese código.
Figura 6.3. Resultado de ejecutar el código del listado 6.12 Ordenar los elementos de la consulta
Otra de las instrucciones que también usaremos con frecuencia es la que nos permite clasificar el resultado de la consulta: orderby. Esta ordenación la podemos hacer de forma ascendente o descen- dente, para ello usaremos las palabras clave ascending o descending respectivamente, (si no se indica ninguna de estas dos instrucciones, el valor predeterminado será ascendente). En el listado 6.13 vemos un ejemplo de uso de estas instrucciones de ordenación.
var res3 = from n in nums where n > 4 orderby n descending select new { Valor = "Número " + n, EsPar = n % 2 == 0 };
Listado 6.13. Consulta con ordenación de los elementos
No es oro todo lo que reluce
Las nuevas instrucciones de consulta de C# en realidad son una forma “amigable” de utilizar todo lo que hemos visto en los capítulos anteriores, y en realidad, son instrucciones que el compilador final- mente convierte en llamadas a métodos extensores aderezados con funciones anónimas. Por ejemplo, la consulta del listado 6.14 también la podríamos escribir como la mostrada en el listado 6.15.
var res4 = from n in nums where n > 4
orderby n descending
select n;
Listado 6.14. Una consulta LINQ con las instrucciones de C#
OrderByDescending(n => n). Select(n => n);
Listado 6.15. La consulta del listado 6.14 utilizando métodos extensores y expresiones lambda
Como podemos comprobar, es mucho más intuitivo el código del listado 6.14 que el correspondiente al listado 6.15, pero nos sirve para saber qué es lo que en realidad ocurre entre bastidores.
En el siguiente capítulo veremos cada una de las instrucciones de C# que se han incorporado al len- guaje para dar soporte a LINQ.
Versiones
Esta característica solo la podemos usar con .NET Framework 3.5 y debemos tener una referencia a System.Linq.dll (todas las plantillas de proyectos de Visual C# 2008 incluyen esa referencia).