Inicio > .NET Framework, C#, Tutorial > Pininos con LINQ – 1. Conceptos básicos

Pininos con LINQ – 1. Conceptos básicos


LINQ, acrónimo para Language Integrated Query, es el nombre que se le da a ciertas palabras reservadas, métodos y expresiones que contiene el lenguaje C# (versión 3 en adelante), y que permiten realizar consultas, muy similares a SQL, sobre diversas fuentes de datos: bases de datos, archivos XML, e incluso simples colecciones de objetos.

Por ejemplo, en SQL, uno hace una sentencia donde pide qué campos se traen de una o varias tablas, se especifican criterios de búsqueda y finalmente se determina la forma en que los resultados son ordenados. LINQ intenta emular este comportamiento, pero sin limitarse a una fuente de datos en particular.

Evidentemente, LINQ no es otra cosa que azúcar sintáctica. Esto es, al compilar traduce las expresiones LINQ en llamadas a ciertos métodos y regresan un resultado en particular (una colección IEnumerable<T>). De lo anterior se desprende que hay dos formas de usar LINQ: con las palabras reservadas de C#, o bien con llamadas a los métodos (de extensión, por cierto) que se definen en el espacio de nombres System.Linq.

Así pues comencemos con lo básico. En primer lugar, debes saber que LINQ actúa sobre cualquier objeto que implemente la interfaz IEnumerable<T>, donde T es un objeto cualquiera sobre el que queramos operar. Esto lo consigue a través de los métodos de extensión que se encuentran en el espacio de nombres System.Linq. Así, lo primero que hacemos al crear nuestra consulta es indicarle cual es la fuente de datos. Esto lo hacemos a través de la cláusula “from”. Consideremos el siguiente código.

int[] nums = new int[] { 1, 2, 3, 4, 5 };
from num in nums
...etc...

Sabemos que un array de enteros implementa IEnumerable<int>. De hecho, cualquier array de tipo T implementa IEnumerable<T>. Por lo que podemos utilizar LINQ. En la segunda línea vemos cómo decimos que se tomará el valor de la colección “nums”. El valor “num” es como una variable con la que podemos hacer referencia a cada objeto que pertenece a la colección (análogo a si hiciéramos “foreach (int num in nums)”).

En segundo lugar, para que la consulta esté completa, se necesita indicar qué valores interesa que se regresen (similar a cuando se hace un “select campo1, campo2 from tabla” en SQL). Si queremos que regrese el número tal cual, simplemente hacemos:

int[] nums = new int[] { 1, 2, 3, 4, 5 };
from num in nums
select num;

Y ahí termina nuestra cláusula, por lo que la terminamos con un punto y coma. Como en el select solo regresamos “num”, que es de tipo entero, nuestro valor de retorno de la consulta será un IEnumerable<int>. Por lo que para que la cláusula quede completa, tenemos que asignarla a alguna variable. Ahora sí:

int[] nums = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> query = 
    from num in nums
    select num;

Por supuesto, en el select podemos hacer cualquier transformación que nos venga en gana. Por ejemplo, el siguiente código crea una consulta que regresa la representación en cadena de texto de cada número de la colección.

int[] nums = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<string> query = 
    from num in nums
    select "texto: " + num.ToString();

Ahora bien, consideremos el siguiente ejemplo:

int zero = 0;
int[] nums = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> query = 
    from num in nums
    select num / zero;

Éste código evidentemente divide cada número de la colección entre cero, lo cual –uno pensaría- nos debería lanzar un DivideByZeroException. Si ejecutamos este código así tal cual, y lo depuramos, vemos que cuando se asigna el valor a query no se lanza ninguna excepción, como cabría esperar. Esto es así debido a que cuando creamos la consulta LINQ, ésta no se ejecuta hasta que iteramos sobre los resultados, sobre query (por ejemplo, hasta que hacemos un foreach). En el siguiente código, obtenemos un DivideByZeroException en la línea 6, no antes.

int zero = 0;
int[] nums = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> query = 
    from num in nums
    select num / zero;
foreach (int i in query)
    Console.WriteLine(i);

Hasta ahorita hemos visto como transformar datos dada una consulta. Un aspecto importante de ésta es el poder filtrar los datos que se deben obtener de la colección. Para ello, utilizamos una cláusula “where”, la cual debe ser seguida por una expresión que se evalúe a verdadero o falso. Para cada elemento de la colección, se evalúa dicha expresión, y si ésta es verdadera entonces se ejecuta el select para ese elemento. Si no, se descarta y se pasa al siguiente.

La cláusula where debe estar entre el from y el select. El siguiente ejemplo obtiene todos los números pares de la colección.

int[] nums = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> query = 
    from num in nums
    where num % 2 == 0
    select num;

Cuando se ejecuta la consulta, recorremos la colección. Para el primer elemento, se evalúa “1 % 2 == 0”, lo cual es falso, por lo que “1” no se incluye en la colección de regreso. Algo similar ocurre para 3 y 5. Pero 2 y 4 sí cumplen, por lo que “query” contendrá solo dos números: 2 y 4. En otras palabras, al ser compilada, la anterior consulta generaría un pseudo-código similar a este:

int[] nums = new in[] { 1, 2, 3, 4, 5 };
List<int> query = new List<int>();
foreach (int num in nums)
{
  if (num % 2 == 0)
    query.Add(num);
}

Más o menos esa es una aproximación al código que generaría el compilador.

También podemos incluir criterios de ordenamiento utilizando la cláusula orderby … ascending y orderby … descending para ordenar de forma ascendente o descendente, respectivamente. Para poder ordenar de esta forma, el tipo de dato del objeto seleccionado en el orderby debe implementar la interfaz IComparable (o IComparer, en su defecto). Si no se implementa IComparable, se utilizará el comparador por defecto que tenga asociado el tipo de dato en cuestión. Por supuesto, int sí implementa esta interfaz, por lo que podemos ahora ordenar nuestros números pares de forma descendente, por ejemplo.

int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IEnumerable<int> query = 
    from num in nums
    where num % 2 == 0
    orderby num descending
    select num;

Las consultas pueden ser más complejas incluso, realizando joins y groups, pero eso es más avanzado y será otro tema de la serie “Pininos en LINQ”.

Por supuesto, podemos hacer consultas sobre tipos de dato que nosotros hayamos definido, como clases y estructuras. El siguiente ejemplo es un programa que utiliza una clase, Employee, y genera una consulta que obtiene todos los objetos que representen a mujeres.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Fermasmas.Wordpress.Com
{
  enum Gender { Male, Female, Unknown }

  class Employee : IComparable
  {
    public string Name;
    public int Age;
    public float Income;
    public Gender Gender;

    public Employee()
      : this(string.Empty, 0, 0F, Gender.Unknown)
    {
    }

    public Employee(string name, int age, float income, Gender gender)
    {
      Name = name;
      Age = age;
      Income = income;
      Gender = gender;
    }

    public int CompareTo(object obj)
    {
      if (obj == null)
        throw new ArgumentNullException("obj");
      if (!(obj is Employee))
        throw new ArgumentException("obj debe ser de tipo Employee", "obj");

      Employee emp = obj as Employee;
      return Name.CompareTo(emp.Name);
    }

    public override string ToString()
    {
      return Name;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
        List<Employee> emps = new List<Employee>()
        {
          new Employee("Fernando Gómez", 27, 20000F, Gender.Male),
          new Employee("Katia De Montanaro", 29, 30000F, Gender.Female),
          new Employee("Moisés Pedraza", 30, 25000F, Gender.Male),
          new Employee("Joan Hernández", 25, 20000F, Gender.Male),
          new Employee("Claudia Estrella", 27, 15000F, Gender.Female)
        };

        IEnumerable<Employee> query =
        from emp in emps 
        where emp.Gender == Gender.Female
        orderby emp.Age ascending
        select emp;

        Print(query);

        Console.ReadKey(true);
    }

    static void Print<T>(IEnumerable<T> query)
    {
        foreach (T t in query)
            Console.WriteLine(t);
    }
  }
}

Al correr este ejemplo, obtenemos:

Claudia Estrella
Katia De Montanaro

Como podemos ver, LINQ es una forma muy buena y fácil no solo para realizar consultas sobre colecciones, sino que también para ordenar nuestros datos de forma rápida y elegante.

Pero aún hay más. Hasta el momento hemos utilizado la cláusula “select” para regresar los objetos completos (como un número entero o la clase Employee del ejemplo anterior). Sin embargo, ¿qué pasa si solo requiero un subconjunto del objeto? Por ejemplo, ¿qué pasa si solo me interesa obtener la propiedad Name del objeto Employee? Bueno, pues podemos combinar con LINQ un aspecto de C# conocido como tipos anónimos. Éstos no son muy útiles por sí solos, pero combinados con LINQ tenemos una forma elegante y potente no solo de realizar consultas, sino que además podemos hacerlas sobre propiedades específicas de un objeto.

Para llevar esto a cabo, basta con crear un tipo anónimo en la cláusula “select” e inicializarlo con el objeto parámetro (aquel que anteriormente simplemente devolvíamos). Sin embargo, dado que un tipo anónimo no puede ser referenciado por su nombre, no podemos crear una variable del tipo IEnumerable<T> que reciba la consulta, porque no sabemos el nombre de “T”. Pero podemos utilizar un tipo de dato variable para que el compilador se encargue de determinar el tipo de dato en base al tipo devuelto por la consulta, lo cual es perfectamente legal. Así pues, podemos modificar el ejemplo anterior para que nos devuelva solamente el nombre y el género. Esto lo haríamos de la siguiente forma.

var query =
  from emp in emps 
  where emp.Gender == Gender.Female
  orderby emp.Age ascending
  select new { Data = emp.Name + " - " + emp.Gender };
foreach (var result in query)
  Console.WriteLine(result.Data);

Como puedes observar, la línea 1 ha cambiado y ahora utilizamos “var” para que no haya problema con el tipo de dato que devuelve la consulta. Luego, en la línea 5 tenemos la cláusula “select” que regresa un tipo de dato anónimo con una sola propiedad: Data, que se forma de concatenar el nombre del empleado con su género. Y finalmente, en la línea 6 tenemos que usar “var” de nueva cuenta para iterar sobre la consulta, por lo mismo que en la línea 1.

Lo que hemos mostrado al momento es solo la superficie de LINQ, pero creo que es suficiente para mostrar su poder y sobre todo, la facilidad con la que ahora podemos buscar, filtrar y ordenar datos de una colección. Seguramente, si no conocías LINQ, te has dado cuenta del mundo de posibilidades que ésto abre: dado que LINQ opera sobre cualquier colección (i.e. cualquier objeto enumerable, es decir, que implemente IEnumerable<T>), es posible crear clases de lógica de negocio que obtengan datos de cualquier fuente (como bases de datos o un archivo XML) y que sean enumerables. En efecto, aplicaciones particulares de LINQ son LINQ to SQL, que permite utiliar LINQ para realizar consultas a bases de datos Microsoft SQL Server, LINQ to DataSet para realizar consultas sobre objetos System.Data.DataSet de ADO.NET, o LINQ to XML para realiar consultas sobre archivos XML. En apuntes subsiguientes iremos explicando esto y más, como parte de este tutorial.

Y ya para finalizar. En este apunte vimos los conceptos básicos de LINQ. Mencionaba yo al inicio que LINQ es “azúcar sintáctica”, y que el compilador traducía consultas a llamadas a métodos de extensión. En el siguiente apunte explicaré qué hay tras bambalinas, y qué métodos son los que llama LINQ. Más aún, veremos cómo utilizar las clases propias de LINQ (dentro de System.Linq) para realizar las mismas consultas, pero con llamadas a métodos de extensión en lugar de cláusulas from…select. No te lo pierdas.

Categorías:.NET Framework, C#, Tutorial Etiquetas: ,
  1. Aún no hay comentarios.
  1. abril 21, 2010 a las 3:42 pm
  2. mayo 3, 2010 a las 11:13 pm

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s