Colabora .NET

Implementando Generics en VB.NET

 

Fecha: 07/Ago/2006 (07/08/2006)
Autor: Horacio N. Hdez.

 


Introducción

En 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):

  1. En el caso de haber más de un parámetro de tipo no podemos “pasar”  de especificar uno, o se especifican todos o ninguno.
  2. El compilador puede deducir pero no puede pensar (sino no tendrías que leer esto) si el parámetro es una matriz unidimensional de tipo genérico y le pasamos una matriz bidimensional, recibiremos un error, igual ocurriría si en el ejemplo anterior la llamada hubiese sido así:
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.

C

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

 



ir al índice principal del Guille