Curso Linq

  • View
    31

  • Download
    13

Embed Size (px)

DESCRIPTION

Curso Linq

Text of Curso Linq

Introduccin a los generics en C# Comenzaremos hablando de los generics en el lenguaje C#, que son el mecanismo de implementacin de clases parametrizadas. Los generics son el mecanismo de implementacin de clases parametrizadas introducido en la versin 2.0 del lenguaje C#. Una clase parametrizada es exactamente igual a una clase de las habituales, salvo por un pequeo detalle: su definicin contiene algn elemento que depende de un parmetro que debe ser especificado en el momento de la declaracin de un objeto de dicha clase. Esto puede resultar extremadamente til a la hora de programar clases genricas, capaces de implementar un tipado fuerte sin necesidad de conocer a priori los tipos para los que sern utilizadas. Confuso? Mejor lo vemos con un ejemplo. Sabemos que un ArrayList es un magnfico contenedor de elementos y que, por suerte o por desgracia, segn se vea, trabaja con el tipo base object. Esto hace que sea posible almacenar en l referencias a cualquier tipo de objeto descendiente de este tipo (o sea, todos), aunque esta ventaja se torna inconveniente cuando se trata de controlar los tipos de objeto permitidos. En otras palabras, nada impide lo siguiente: ArrayList al = new ArrayList();al.Add("Siete caballos vienen de Bonanza...");al.Add(7);al.Add(new String('*', 25)); // 25 asteriscosEsto puede provocar errores a la hora de recuperar los elementos de la lista, sobre todo si asumimos que los elementos deben ser de un determinado tipo. Y claro, el problema es que el error ocurrira en tiempo de ejecucin, cuando muchas veces es demasiado tarde:foreach (string s in al){ System.Console.WriteLine(s);} Efectivamente, se lanza una excepcin indicando que "No se puede convertir un objeto de tipo 'System.Int32' al tipo 'System.String'". Lgico. Obviamente eso se puede solucionar fcilmente, por ejemplo creando nuestra propia coleccin partiendo de CollectionBase (o similar) y mostrar mtodos de acceso a los elementos con tipado fuerte, o bien, usando delegacin, crear una clase de cero que implemente interfaces como IEnumerable en cuyo interior exista una coleccin que es la que realmente realiza el trabajo. En cualquier caso, es un trabajazo, puesto que por cada clase que queramos contemplar deberamos crear una clase especfica, tal y como se describe en el prrafo anterior. Y aqu es donde los generics entran en escena. El siguiente cdigo declara una lista de elementos de tipo AlgoComplejo: List lista = new List();AlgoComplejo algo = new AlgoComplejo(); lista.Add(algo);lista.Add(new AlgoComplejo());lista.Add("blah"); // Error en compilacin! Con esta declaracin, no ser posible aadir a la lista objetos que no sean de la clase indicada, ni tampoco ser necesario realizar un cast al obtener los elementos, pues sern directamente de ese tipo. Es interesante ver la gran cantidad de clases genricas para el tratamiento de colecciones que se incorporaron con la versin 2.0 del framework en el espacio de nombres System.Collections.Generic, y su amplia utilizacin a lo largo del marco de trabajo. Creacin de clases genricasEn los ejemplos anteriores hemos visto cmo utilizar las clases genricas proporcionadas por el framework, pero, y si queremos nosotros crear una clase genrica propia? Veremos que es muy sencillo. Vamos a desarrollar un ejemplo completo donde podamos ver las particularidades sintcticas y detalles a tener en cuenta. Crearemos la clase CustodiadorDeObjetos, cuya misin es almacenar un objeto genrico y permitirnos recuperarlo en cualquier momento. Bsicamente, construiremos una clase con una variable de instancia y un getter y setter para acceder a la misma, pero usaremos los generics para asegurar que valga para cualquier tipo de datos y que el objeto introducido sea siempre del mismo tipo que el que se extrae: public class CustodiadorDeObjetos{ private T objeto; public T Objeto { get { return objeto; } set { this.objeto = value; } }} Como podemos ver, en la propia definicin de la clase indicamos que sta ser una plantilla que recibir como parmetro genrico un tipo al que hemos llamado T en el ejemplo anterior. Se puede apreciar cmo hemos declarado un miembro privado llamado objeto, de tipo T, al que se puede acceder a travs del correspondiente getter y setter. El tipo concreto sobre el que actuaremos se definir en el momento de la instanciacin de un objeto, puesto que habr que indicarlo expresamente: // Crea un custodiador de strings...CustodiadorDeObjetos cs = new CustodiadorDeObjetos();// Creamos un custodiador de intsCustodiadorDeObjetos ci = new CustodiadorDeObjetos();// Creamos un custodiador de CosasCustodiadorDeObjetos cp = new CustodiadorDeObjetos(); De esta forma, evitamos tener que crear clases especficas para cada tipo de datos con el que necesitemos trabajar, ahorrndonos muchsimo esfuerzo y manteniendo todas las ventajas que el tipado fuerte nos ofrece. Y para que os hagis una idea del potencial de esta tcnica, pensad que si no existiera la genericidad (como ocurra en versiones anteriores del lenguaje), nos veramos obligados a implementar clases especficas como las siguientes: public class CustodiadorDeStrings{ private string objeto; public string Objeto { get { return objeto; } set { this.objeto = value; } }}public class CustodiadorDeCosas{ private Cosa objeto; public Cosa Objeto { get { return objeto; } set { this.objeto = value; } }}// ... etc. El siguiente cdigo muestra la utilizacin de la nueva clase genrica CustodiadorDeObjetos que definimos con anterioridad: CustodiadorDeObjetos cs = new CustodiadorDeObjetos();cs.Objeto = "Hola"; // Asignamos directamentestring s = cs.Objeto; // No hace falta un cast, // puesto que Objeto es de tipo string

cs.Objeto = 12; // Error en compilacin, // Objeto es de tipo stringCustodiadorDeObjetos ci = new CustodiadorDeObjetos();ci.Objeto = 12; // Asignamos directamenteint i = cs.Objeto; // No hace falta un cast, pues Objeto es int

cs.Objeto = "Hola"; // Error en compilacin, // Objeto es de tipo int Restriccin de tipos en genericsUna particularidad interesante en la declaracin de los generics en C# es la posibilidad de establecer limitaciones en los tipos utilizados como parmetros de las plantillas. Aunque resulte paradjico, el propio lenguaje facilita la creacin de clases genricas que no lo sean tanto, lo cual puede resultar realmente til en mltiples escenarios. Y para verle el sentido a esto, utilicemos el siguiente ejemplo, aparentemente correcto: public class Seleccionador{ public T Mayor(T x, T y) { int result = ((IComparable)x).CompareTo(y); if (result > 0) return x; else return y; }} Se trata de una clase genrica abierta, cuya nica operacin (Mayor(...)) permite obtener el objeto mayor de los dos que le pasemos como parmetros. El criterio comparativo lo pondr la propia clase sobre la que se instancie la plantilla: los enteros sern segn su valor, las cadenas segn su orden alfabtico, etc. A continuacin creamos dos instancias partiendo de la plantilla anterior, y concretando el generic a los tipos que nos hacen falta: Seleccionador selInt = new Seleccionador();Seleccionador selStr = new Seleccionador(); Estas dos instanciaciones son totalmente correctas, verdad? Si despus de ellas usamos el siguiente cdigo: Console.WriteLine(selInt.Mayor(3, 5));Console.WriteLine(selStr.Mayor("X", "M")); Obtendremos por consola un 5 y una X. Todo perfecto; aparece, para cada llamada, la conversin a cadena del objeto mayor de los dos que le hayamos enviado como parmetros. El problema aparece cuando instanciamos la clase genrica para un tipo que no implementa IComparable, que se utiliza en el mtodo Mayor() para determinar el objeto mayor de ambos. En este caso, se lanza una excepcin en ejecucin indicando que el cast hacia IComparable no puede ser realizado, abortando el proceso. Por ejemplo: public class MiClase // No es comparable{}

[...]Seleccionador sel = new Seleccionador();MiClase x1 = new MiClase();MiClase x2 = new MiClase();Console.WriteLine(selString.Mayor(x1, x2)); // Excepcin, // no son // comparables! Una posible solucin sera, antes del cast a IComparable en el mtodo Mayor(), hacer una comprobacin de tipos y realizar la conversin slo si es posible, pero, y qu hacemos en caso contrario? devolver un nulo? La pregunta no creo que tenga una respuesta sencilla, puesto que en cualquier caso, se estaran intentado comparar dos objetos que no pueden ser comparados. La solucin ptima, como casi siempre, es controlar en tiempo de compilacin lo que podra ser una fuente de problemas en tiempo de ejecucin. La especificacin de C# incluye la posibilidad de definir constraints o restricciones en la declaracin de la clase genrica, limitando los tipos con los que el generic podr ser instanciado. Y, adems, de forma bastante simple, nada ms que aadir en la declaracin de la clase Seleccionador la siguiente clusula where: public class Seleccionador where T: IComparable // !Slo permitimos comparables!{ public Tipo Mayor(Tipo x, Tipo y) [...] Existen varios tipos de restricciones que podemos utilizar para limitar los tipos permitidos para nuestros tipos genricos: where T: struct, indica que el argumento debe ser un tipo valor. where T: class, indica que T debe ser un tipo referencia. where T: new(), fuerza a que el tipo T disponga de un constructor pblico sin parmetros; es til cuando desde dentro de algn mtodo de la clase se pretende instanciar un objeto del mismo. where T: nombredeclase, indica que el tipo T debe heredar o ser de dicha clase. where T: nombredeinterfaz, T deber implementar el interfaz indicado. where T1: T2, indica que el argumento T1 debe ser igual o heredar del tipo, tambin parmetro genrico de la clase, T2.Estas restricciones pueden combinarse, de forma que si queremos que un parmetro genrico se cia a varias de ellas, simplemente las separamos por coma en la clusula where: public class Seleccionador where T: IComparable, new () // Slo permitimos comparables, // instanciable sin parmetros Estas informaciones se completarn en el siguiente artculo