En Noviembre del 2007 junto con el lanzamiento de .NET Framework 3.5 se presentó LINQ (Language INtegrated Query) como una de las novedades de ésta Release. Muchas de las características de LINQ fueron originalmente probadas con el lenguaje creado por Microsoft Research denominado Cω.
LINQ proporciona de forma nativa la capacidad de realizar consultas al estilo SQL desde el propio lenguaje de forma agnóstica al propio sistema de almacenamiento, es decir que tanto podemos utilizar sobre orígenes o conjuntos de datos relacionales como objetos almacenados en memoria así como un amplio abanico de proveedores que van desde documentos XML hasta NHibernate pasando por Twitter o Amazon.
Sin embargo, tras LINQ se esconden algunos conceptos que debemos tener claros para sacarle el máximo provecho a este componente; métodos de extensión, tipos anónimos o las expresiones lambda son sólo algunos ejemplos que vamos a tratar de desvelar en esta serie de artículos y en el que empezaremos conociendo los métodos de extensión.
Características
Los métodos de extensión forman parte de las especificaciones del lenguaje en C# y VB.NET y conforman una de las características más importantes de LINQ. La idea de los métodos de extensión es la de añadir métodos sobre tipos existentes sin tener la necesidad de crear nuevos tipos derivados. Básicamente es un método estático que se llama sobre el tipo extendido. Este es un punto importante y clave para entender la diferencia entre métodos estáticos y métodos de extensión. Es habitual que en algunas formaciones que he impartido éste sea un punto de discusión y expongo el siguiente ejemplo para clarificar la diferencia y motivos de los métodos de extensión.
Imaginemos que tenemos la típica clase repleta de métodos estáticos y que normalmente conocemos como Utiles. Obviemos lo trivial de clase y centrémonos en el contenido que asumimos será reutilizada en varias rutinas dentro de nuestro programa. La clase Utiles en cuestión es:
public class Utiles
{ public static double ElevarA(int numA,int numB) { return Math.Pow(numA, numB); }public static double CuadradoDe(int numA) { return Math.Pow(numA, 2); } public static double Multiplicar(int numA, int numB) { return numA * numB; } public static double Dividir(double numA, double numB) { return numA / numB; } public static double CocienteDe(int numA, int numB) { return numA % numB; }
}
La forma en la que la utilizaremos no contiene ningún misterio y algunos ejemplos los podemos ver a continuación:
static void Main(string[] args)
{ int numeroA = 5; int numeroB = 6;Console.WriteLine(”{0} elevado a {1} es {2}”, numeroA,numeroB, Utiles.ElevarA(numeroA ,numeroB)); Console.Read();
}
La primera pregunta que nos hacemos es, si esta forma de tener métodos estáticos ya nos es útil, ¿qué papel juegan los métodos de extensión? Bien en primer lugar por claridad de código. Como dije anteriormente los métodos de extensión extienden (valga la redundancia) los tipos con lo que si en lugar de la típica clase Utiles hacemos uso de los métodos de extensión el ejemplo de código mostrado anteriormente quedaría “más claro”. Veamos la clase estática con los métodos extensores y luego el uso de los mismos:
public static class ExtensionClass
{ public static double ElevadoA(this int numA, int numB) { return Math.Pow(numA, numB); }public static double AlCuadrado(this int numA) { return Math.Pow(numA, 2); } public static double MultiplicadoPor(this int numA, int numB) { return numA * numB; } public static double DivididoPor(this double numA, double numB) { return numA / numB; } public static double CocienteDe(this double numA, double numB) { return numA % numB; }
}
Ahora el uso de algunos de los métodos:
static void Main(string[] args)
{ int numeroA = 5; int numeroB = 6;Console.WriteLine(”{0} elevado a {1} es {2}”, numeroA, numeroB, numeroA.ElevadoA(numeroB)); Console.Read();
}
Bien, si, algo más claro queda el código; además también podemos hacer uso del Intellisense de Visual Studio .NET para el objeto de tipo extendido pero, honestamente, ésta no es la principal característica de los métodos extendidos.
Tomemos el siguiente ejemplo de código. Aquí se realizan varias operaciones aritméticas haciendo uso de la clase Utiles:
static void Main(string[] args)
{ int numeroA = 5; int numeroB = 6;double resultado = Utiles.CuadradoDe((int) Utiles.Multiplicar(numeroA, numeroB)); resultado = Utiles.CocienteDe((int)res, 16); Console.WriteLine(resultado); Console.Read();
}
Si hacemos lo mismo con los métodos de extensión el resultado es:
static void Main(string[] args)
{ int numeroA = 5; int numeroB = 6;double resultado = ((int) numeroA.MultiplicadoPor(numeroB)).AlCuadrado().CocienteDe(16); Console.WriteLine(resultado); Console.Read();
}
Como veis ésta si es la principal utilidad de los métodos de extensión y no es más que la agregación de métodos sobre el tipo extendido sin necesidad de crear tipos derivados. Pues bien, esta característica del lenguaje es fundamental para LINQ. A continuación se muestra un detalle de uso de métodos de extensión de IEnumerable<T>, de la cual hablaremos en el siguiente post, en LINQ con los métodos Where, OrderByDescending y Select.
//creamos un origen de datos IEnumerable<cliente> list = new List<cliente> { new Cliente {IdProvincia = 1, Nombre = “Cliente1”, Tipo = 10, VolumenNegocio = 100.00m}, new Cliente {IdProvincia = 2, Nombre = “Cliente2”, Tipo = 20, VolumenNegocio = 20.00m}, new Cliente {IdProvincia = 3, Nombre = “Cliente3”, Tipo = 20, VolumenNegocio = 230.00m}, new Cliente {IdProvincia = 3, Nombre = “Cliente4”, Tipo = 20, VolumenNegocio = 500.00m}, new Cliente {IdProvincia = 1, Nombre = “Cliente5”, Tipo = 30, VolumenNegocio = 10.00m}, new Cliente {IdProvincia = 2, Nombre = “Cliente6”, Tipo = 10, VolumenNegocio = 750.00m}, new Cliente {IdProvincia = 1, Nombre = “Cliente7”, Tipo = 20, VolumenNegocio = 340.00m}, new Cliente {IdProvincia = 1, Nombre = “Cliente8”, Tipo = 20, VolumenNegocio = 170.00m} }; //obtenemos el nombre de los clientes de la provincia 1 IEnumerable<string> resultado = list.Where(s => s.IdProvincia == 1) //ordenados descendente en base al volumen de negocio .OrderByDescending(d => d.VolumenNegocio).Select(s=> s.Nombre); // //Resultado: // //Cliente7 //Cliente8 //Cliente1 //Cliente5
Sobrecarga
Por último, los métodos de extensión tienen menos prioridad a nivel de compilación sobre los métodos de tipo en caso de que la firma sea idéntica (sobrecarga). Es decir, si tenemos la clase Cliente con un método con firma:
public class Cliente
{
public void DarDeBaja(Datetime fecha) { //TODO }
}
y extendemos sobre el tipo Cliente un método idéntico:
public class Extensiones
{
public void DarDeBaja(this Cliente cilente, Datetime fecha) { //TODO }
}
El compilador utilizará siempre el método primitivo de la clase Cliente. El motivo es que el compilador busca primero entre los métodos de instancia del propio tipo.