> Manuales > Manual sobre la plataforma .Net

Estamos hablando sobre características fundamentales de los lenguajes C# y VB.NET, y en esta ocasión vamos a hablar sobre los métodos genéricos de VB.NET.

Estamos hablando sobre características fundamentales de los lenguajes C# y VB.NET, en varios artículos que vienen a completar la información que se encuentra en el Manual de .NET de DesarrolloWeb.com. En esta ocasión vamos a hablar sobre los métodos genéricos de VB.NET.

Al escribir el artículo "Métodos genéricos en C#", cuya versión original publiqué en mi propio blog Variable Not Found, estuve pensando en tratar este tema también en VB.NET de forma simultánea, pero al final preferí limitarme a C# para no hacer la entrada más extensa de lo que ya iba a resultar de por sí.

Por esta razón, en este artículo nos centraremos exclusivamente en este popular lenguaje.

Los métodos genéricos son interesantes herramientas que están con nosotros desde los tiempos del .NET Framework 2.0 y pueden resultarnos muy útiles de cara a la construcción de frameworks o librerías reutilizables.

Podríamos considerar que un método genérico es a un método tradicional lo que una clase genérica a una tradicional; por tanto, se trata de un mecanismo de definición de métodos con tipos parametrizados, que nos ofrece la potencia del tipado fuerte en sus parámetros y devoluciones aun sin conocer los tipos concretos que utilizaremos al invocarlos.

Vamos a profundizar en el tema desarrollando un ejemplo, a través del cual podremos comprender por qué los métodos genéricos pueden sernos muy útiles para solucionar determinado tipo de problemas, y describiremos ciertos aspectos, como las restricciones o la inferencia, que nos ayudarán a sacarles mucho jugo.

Escenario de partida

Como sabemos, los métodos tradicionales trabajan con parámetros y retornos fuertemente tipados, es decir, en todo momento conocemos los tipos concretos de los argumentos que recibimos y de los valores que devolvemos. Por ejemplo, en el siguiente código, vemos que el método Maximo, cuya misión es obvia, recibe dos valores Integer y retorna un valor del mismo tipo:

Function Maximo(ByVal uno As Integer, ByVal otro As Integer) _
      As Integer
      If uno > otro Then Return uno
      Return otro
End Function

Hasta ahí, todo correcto. Sin embargo, está claro que retornar el máximo de dos valores es una operación que podría ser aplicada a más tipos, prácticamente a todos los que pudieran ser comparados. Si quisiéramos generalizar este método y hacerlo accesible para otros tipos, se nos podrían ocurrir al menos dos formas de hacerlo.

La primera sería realizar un buen puñado de sobrecargas del método para intentar cubrir todos los casos que se nos puedan dar:

Function Maximo(ByVal uno As Integer, ByVal otro As Integer) _
      As Integer
      ' ...
End Function
Function Maximo(ByVal uno As Long, ByVal otro As Long) _
      As Long
      ' ...
End Function
Function Maximo(ByVal uno As Decimal, ByVal otro As Decimal) _
      As Decimal
      ' ...
End Function

      ' Y así hasta que te aburras...

Obviamente, sería un trabajo demasiado duro para nosotros, desarrolladores perezosos como somos. Además, según Murphy, por más sobrecargas que creáramos seguro que siempre nos faltaría al menos una: justo la que vamos a necesitar ;-).

Otra posibilidad sería intentar generalizar utilizando las propiedades de la herencia. Es decir, si asumimos que tanto los valores de entrada del método como su retorno son del tipo base Object, aparentemente tendríamos el tema resuelto. Lamentablemente, al finalizar nuestra implementación nos daríamos cuenta de que no es posible hacer comparaciones entre dos Object's, por lo que, o bien incluimos en el cuerpo del método código para comprobar que ambos sean comparables (consultando si implementan IComparable), o bien elevamos el listón de entrada a nuestro método, así:

Function Maximo(ByVal uno As IComparable, ByVal otro As Object) As Object
      If uno.CompareTo(otro) > 0 Then Return uno
      Return otro
End Function

Pero efectivamente, como ya habréis notado, esto tampoco sería una solución válida para nuestro caso. En primer lugar, el hecho de que ambos parámetros sean Object o IComparable no asegura en ningún momento que sean del mismo tipo, por lo que podría invocar el método enviándole, por ejemplo, un String y un Integer, lo que provocaría un error en tiempo de ejecución. Y aunque es cierto que podríamos incluir código que comprobara que ambos tipos son compatibles, ¿no tendríais la sensación de estar llevando a tiempo de ejecución problemática de tipado que bien podría solucionarse en compilación?

El método genérico

Fijaos que lo que andamos buscando es simplemente alguna forma de representar en el código una idea conceptualmente tan sencilla como: "mi método va a recibir dos objetos de un tipo cualquiera T, que implemente IComparable, y va a retornar el que sea mayor de ellos". En este momento es cuando los métodos genéricos acuden en nuestro auxilio, permitiendo definir ese concepto como sigue:

Function Maximo(Of T As IComparable) _
      (ByVal uno As T, ByVal otro As T) As T
      If uno.CompareTo(otro) > 0 Then Return uno
      Return otro
End Function

En el código anterior, podemos distinguir una porción de código que aparece resaltada justo después del nombre del método, y antes de comenzar a definir sus parámetros. Es la forma de indicar que Maximo es un método genérico y operará sobre un tipo cualquiera al que llamaremos T, y mediante una restricción estamos indicando que deberá implementar obligatoriamente el interfaz IComparable (más adelante trataremos esto en profundidad).

A continuación, podemos observar que los dos parámetros de entrada son del tipo T, así como el retorno de la función. Si no lo ves claro, sustituye mentalmente la letra T por Integer (por ejemplo) y seguro que mejora la cosa.

Lógicamente, estos métodos pueden presentar un número indeterminado de parámetros genéricos, como en el siguiente ejemplo. Observad que la palabra clave Of sólo se indica al principio:

Function MiMetodo(Of T1, T2, TResult) _
      (ByVal par1 As T1, ByVal par2 As T2) As TResult

Y una aclaración antes de continuar: lo de usar la letra T para identificar el tipo es pura convención, podríamos llamarlo de cualquier forma (por ejemplo Maximo(Of MiTipo)(ByVal uno as MiTipo, ByVal otro as MiTipo) As MiTipo), aunque ceñirse a las convenciones de codificación es normalmente una buena idea.

Restricciones en parámetros genéricos

Retomemos un momento el código de nuestro método genérico:

Function Maximo(Of T As IComparable) _
      (ByVal uno As T, ByVal otro As T) As T
      If uno.CompareTo(otro) > 0 Then Return uno
      Return otro
End Function

Antes había comentado que en este caso estábamos creando un método que podría actuar sobre cualquier tipo, aunque mediante una restricción forzábamos a que éste implementara, obligatoriamente, el interfaz IComparable, lo que nos permitiría realizar la operación de comparación que necesitamos.

Obviamente, las restricciones no son obligatorias; de hecho, sólo debemos utilizarlas cuando necesitemos limitar de alguna forma los tipos permitidos como parámetros genéricos, como en el ejemplo anterior. Está permitida la utilización de las siguientes reglas:

Un último detalle relativo a esto: a un mismo parámetro se pueden aplicar varias restricciones, en cuyo caso se introducirán entre llaves ("{" y "}") separadas por comas, como aparece en el siguiente ejemplo:

Function MiMetodo(Of T1 As {Class, IEnumerable}, _
      T2 As {T1, New}, _
      TResult As New) _
      (ByVal par1 As T1, ByVal par2 As T2) As TResult
      ' ... Cuerpo del método
End Function

Uso de métodos genéricos

A estas alturas ya sabemos, más o menos, cómo se define un método genérico, pero nos falta aún conocer cómo podemos consumirlos, es decir, invocarlos desde nuestras aplicaciones. Aunque puede intuirse, la llamada a los métodos genéricos debe incluir tanto la tradicional lista de parámetros del método como los tipos que lo concretan. Vemos unos ejemplos:

Dim mazinger As String = Maximo(Of String)("Mazinger", "Afrodita")
Dim i99 As Integer = Maximo(Of Integer)(2, 99)

Una interesantísima característica de la invocación de estos métodos es la capacidad del compilador para inferir, en muchos casos, los tipos que debe utilizar como parámetros genéricos, evitándonos tener que indicarlos de forma expresa. El siguiente código, totalmente equivalente al anterior, aprovecha esta característica:

Dim mazinger As String = Maximo("Mazinger", "Afrodita")
Dim i99 As Integer = Maximo(2, 99)

El compilador deduce el tipo del método genérico a partir de los que estamos utilizando en la lista de parámetros. Por ejemplo, en el primer caso, dado que los dos parámetros son String, puede llegar a la conclusión de que el método tiene una signatura que coincide con la definición del genérico, utilizando String como tipo parametrizado.

Otro ejemplo de método genérico

Veamos un ejemplo un poco más complejo. El método CreaLista, aplicable a cualquier clase, retorna una lista genérica (List(Of T)) del tipo parametrizado del método, que rellena inicialmente con los argumentos (variables) que se le suministra:

Function CreaLista(Of T)(ByVal ParamArray pars() As T) As List(Of T)
      Dim list As New List(Of T)
      For Each elem As T In pars
            list.Add(elem)
      Next
      Return list
End Function

      ' ...
      ' Uso:
      Dim nums = CreaLista(Of Integer)(1, 2, 3, 4, 5, 6, 7)
      Dim noms = CreaLista(Of String)("Pepe", "Juan", "Luis")

Otros ejemplos de uso, ahora beneficiándonos de la inferencia de tipos:

Dim nums = CreaLista(1, 2, 3, 4, 5, 6, 7)
Dim noms = CreaLista("Pepe", "Juan", "Luis")

' Incluso con tipos anónimos de VB.NET 9
Dim v = CreaLista( _
      New With {.X = 1, .Y = 2}, _
      New With {.X = 3, .Y = 4} _
)
Console.WriteLine(v(1).Y) ' Muestra "4"

En resumen, se trata de una característica de la plataforma .NET, reflejada en lenguajes como C# y VB.Net, que está siendo ampliamente utilizada en .NET, y a la que hay que habituarse para poder sacar partido al marco de trabajo.

José María Aguilar

Consultor y desarrollador independiente, Most Valuable Professional (MVP) en ASP...

Manual