Colabora .NET |
Implementando Generics en VB.NET
Fecha: 07/Ago/2006 (07/08/2006)
|
IntroducciónEn este articulo examinaremos una de las nuevas características del .net framework 2.0, se trata de Generics... Comenzando...La nueva característica del .net framework llamada Generics nos permite mejorar el rendimiento en tiempo de ejecución cuando implementamos métodos o clases que tengan que manejar tipos de datos que no necesariamente estén relacionados. Un ejemplo bien claro de esto lo tenemos si empleamos un ArrayList, que no es mas que un matriz optimizada para crecer dinámicamente, aunque tiene el inconveniente de que el tipo que maneja es Object, esto implica que cada vez que insertemos un dato o lo extraigamos, se debe llevar a cabo una conversión (boxing, unboxing) esto consume recursos y más aun cuando se trata de colecciones. Es aquí donde podemos encontrar una de las tantas ventajas que presenta Generics, ya que podemos crear una colección optimizada para ser dinámica pero que sea capaz de tratar cada dato como el tipo que es, una colección con esta característica ya esta implementada en System.Collections.Generic y recibe el nombre de List. Ahora bien como es que es posible este comportamiento tan genérico en una solo tipo, la respuesta esta en el uso de parámetros de tipo (type parameters), veamos un ejemplo para una mejor comprensión Class MiColeccion(Of T) Sub Add(ByVal Value As T) '... End Sub End Class Lo primero que nos llama la atención es la inclusión de un paréntesis justo al final de la declaración de la Clase MiColeccion, y dentro vemos la palabra of seguida de “T”. Bueno, como ya podrás imaginarte este es un parámetro de Tipo; en cualquier lugar donde intervenga el tipo T será sustituido (mediante el JIT) en tiempo de ejecución por el tipo que pasemos a la hora de crear la instancia. Veamos un ejemplo para que no se te crucen los “tipos”: Dim ColecciónDeString As New MiColeccion(Of String) ColeccionDeString.Add("un string") En este caso en tiempo de ejecución se sustituirá el tipo T por el tipo String y la clase se comportaría de la siguiente manera. Class MiColeccion(Of String) Sub Add(ByVal Value As String) '... End Sub End Class ¿Qué, ya lo has entendido? Bien por ti, pero estamos empezando. Ya sabes de que va esto de Generics, como ves se ahorra mucho código (y con mucho estilo por cierto) y se aumenta el rendimiento del programa. Pasemos ahora a los métodos genéricos. Un método genérico guarda muy poca diferencia con una clase genérica (Ojo, no creas que el método Add es genérico solo porque recibe un parámetro que es de un Tipo Genérico,).Tanto los métodos como las clases genéricas tienen presente la palabra clave Of, lo que en el caso de los métodos estaríamos ya en presencia de dos lista de parámetros, los que estamos acostumbrados a usar y los de reciente adquisición. Al igual que en las clases podemos definir tantos tipos genéricos como necesitamos. La sintaxis seria así: <Modificadores> Sub <Nombre> (Of <Lista de parámetros de Tipo>)(<Lista de Parámetros) '... End Sub Algo importante que debes saber es que Of se pone una sola vez, en caso de varios parámetros de tipo se separan por coma. Por ahí se suele decir que una línea de código valen más que mil páginas de documentación (es broma), así que veamos un ejemplo. Sub IntercambiarValores(Of unTipo)(ByRef val1 as unTipo, ByRef val2 as unTipo) Dim temp As unTipo temp = val1 Val1 = val2 Val2 = temp End Sub En este código vemos como podemos recibir un parámetro que es del tipo que se pasa en la llamada como parámetro de tipo, y que además dentro del bloque de código del método podemos declarar objetos de ese tipo, instanciarlos, hasta matrices. Ahora bien como llamar a este método: Dim int1 As Integer = 6 Dim int2 As Integer = 7 IntercambiarValores(Of Integer)(int1, int2) Pero también podemos “pasar” de especificar el parámetro de tipo y que sea solo: Dim int1 As Integer = 6 Dim int2 As Integer = 7 IntercambiarValores(int1, int2) Aquí se pone de manifiesto una deducción de tipo (Type Inference), esto solo es valido con los métodos, y para hacer uso de ella hay que tener en cuenta ciertas pautas (que seguro las encontraras muy obvias pero siempre es bueno dejar todo claro):
Dim int1 As Integer = 6 Dim str As String = "7" IntercambiarValores(int1, str) Obviamente hay una contradicción, y seremos notificados. Básicamente a la hora de llamar a un método, en tiempo de diseño se nos alertara apropiadamente. Ahora analicemos otra cuestión, es posible usar como tipos normales los parámetros de tipo, aunque solo podemos acceder a los miembros de System.Object ya que todo los tipos en .net heredan implícitamente de esta clase, pero que pasa si necesitamos comparar dos variables, o mejor aun que ocurriría si tuviéramos que implementar un algoritmo de ordenamiento por burbuja. No podríamos usar los operadores (<;>) ya que no están definidos para System.Object. Es en esta situaciones donde necesitamos características por parte de los tipos especificados, es donde entrar a jugar un papel las Obligaciones de Tipo (Type Constraints), que no solo nos permitirá acceder a determinados miembros, sino que evitara que tipos que no implementan las “obligaciones” se filtren en nuestro código. Veamos un ejemplo: Sub Sort(Of T As IComparable)(ByRef arg() As T) For j As Integer = 0 To arg.Length - 1 For k As Integer = arg.Length - 1 To 1 Step -1 If arg(k).CompareTo(arg(k - 1)) = -1 Then IntercambiarValores(arg(k), arg(k - 1)) End If Next Next End Sub Observemos la palabra clave As justo detrás de la declaración del parámetro de tipo T, ¿Qué cambia te preguntas? Pues bien aquí le decimos al compilador que el tipo T debe Implementar la interfaz IComparable de lo contrario cuando pasemos un parámetro de tipo que no implemente esa interfaz recibiremos un error, además de que nos permite acceder a los métodos definidos en IComparable. Luego empleamos el método que implementamos previamente IntercambiarValores. Para comprender todo lo que podemos hacer mediante Constraints, veamos el cuadro siguiente. Interface I1 Sub B() End Interface Interface I2 Sub S1() Sub C() End Interface Class Clase Sub Z() End Sub End Class Class Prueba(Of T As {I1, I2}, T2 As {Clase, New}) Shared Sub metodo1(ByRef obj As T) obj.S1() obj.B() End Sub Shared Sub metodo2(ByRef obj As T2) obj.Z() End Sub End Class Generics no termina aquí, solo podemos trabajar con parámetros de tipo dentro del código de ese tipo, pero también podemos usarlo en los tipos anidados, en el código siguiente. Definimos MiClaseAnidada dentro de MiClase, la cual recibe dos parámetros de tipo y empleamos uno de ellos dentro de la clase anidada. Esto trae consigo que si el tipo anidado MiClase(of String, Integer).MiClaseAnidada y MiClase(of String, Byte).MiClaseAnidada sean tipos diferentes ya que MiClase(of String, Integer) y MiClase(of String, Byte) tambien lo son y MiClaseAnidada utiliza el parámetro de tipo T. Class MiClase(Of T, T1) Class MiClaseAnidada 'Podemos usar el parametro de tipo T 'dentro de una clase anidada Dim variable As T End Class End Class El siguiente código, nos mostraría un mensaje de error ya que se trata de tipos diferentes: Dim Prueba As MiClase(Of String, Integer).MiClaseAnidada = _ New MiClase(Of String, Byte).MiClaseAnidada También podemos sobrecargar los parámetros de tipo y obtener clases sobrecargadas. En el siguiente código tenemos tres clases distintas con el mismo nombre a la que les pasamos un número diferentes parámetros de tipos. Class OtraClase End Class Class OtraClase(Of T1) End Class Class OtraClass(Of T1, T2) End Class Sub Main() Dim v1 As OtraClase(Of String, Integer) 'Se refiere a la clase con 'dos parametros de tipo. Dim v2 As OtraClase(Of String) 'Se refiere a la clase con 'un parametros de tipo. Dim v3 As OtraClase 'Se refiere a la clase que no tiene 'parametros de tipo. End Sub No solo es posible sobrecargar tipos, también es valido para los métodos mediante los tipos por parámetros y opcionalmente combinar esta sobrecarga con la que ya conocemos de versiones previas. Function Metodo(Of T)(ByVal v1 As T, ByVal v2 As T) As Integer '... End Function Function Metodo(Of T, T1)(ByVal v1 As T, ByVal v2 As T1) As Integer '... End Function Function Metodo() As Integer '... End Function Para terminar, debes tener en cuenta que IntelliSense soporta Generics completamente, que viene siendo la guinda de este pastel, para que veas un ejemplo de esto tienes el último código con una colección del tipo Hashtable pero que implementa Generics definida en System.Collections.Generic como Dictionary: Sub Main() Dim diccionario As New _ System.Collections.Generic.Dictionary(Of String, Amigo) Dim nuevoamigo As New Amigo nuevoamigo.Telefono = "8889-7774-441606" nuevoamigo.Apodo = "Saga" diccionario.Add(nuevoamigo.Apodo, nuevoamigo) Console.WriteLine(diccionario.Item("Saga").Telefono) End Sub Class Amigo '... Public Apodo As String Public Telefono As String '... End Class Finalizando...Resumiendo este articulo, Generics nos permite crear código común para diferentes tipos de datos, manteniendo la seguridad y aumentando el rendimiento. Con un soporte por parte de IntelliSense que hará las delicias de cualquier desarrollador. Beneficios todos que pueden ser aprovechados por código antiguo con pocas modificaciones Espacios de nombres usados en el código de este artículo:System.Collections.Generic
|