Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
Mostrando entradas con la etiqueta vb.net. Mostrar todas las entradas
Mostrando entradas con la etiqueta vb.net. Mostrar todas las entradas
martes, 16 de enero de 2018
ASP.NET CoreYa, sé que puede parecer una pregunta extraña a estas alturas, pero el caso es que un alumno del curso de ASP.NET Core en CampusMVP me la envió hace unos días y me ha parecido interesante comentar el asunto por aquí, por si puede interesar a alguien más.

Aunque a muchos nos pueda parecer raro, aún hay bastantes empresas y desarrolladores trabajando en Visual Basic .NET. En ocasiones sólo es para mantener código antiguo, pero otras veces (más de lo que puede parecer) incluso para crear nuevos sistemas aprovechando la experiencia de los desarrolladores y bases de código existente.
Si estáis en esta situación y vais a comenzar a trabajar con ASP.NET Core, mi recomendación siempre es que aprovechéis el momento de cambios para dar el salto a C#. Spoiler: no os arrepentiréis ;)
Bien, respecto a la pregunta motivo del post, en teoría debería poderse, y de hecho ya difundimos por aquí hace tiempo la noticia oficial de que sería soportado, pero la verdad es que desde entonces no he vuelto a oir hablar mucho del tema, ni he visto ejemplos, ni movimiento en la comunidad ni nada parecido, lo cual me hizo dudar de la situación actual de este tema.

En este post vamos a hacer un tour rápido para ver, en la práctica, cómo está el soporte para VB.NET en la versión actual de ASP.NET Core (2.0).
martes, 28 de abril de 2015
ASP.NET CoreSin duda son buenas noticias para los desarrolladores VB.NET, que habían visto cómo su lenguaje favorito era ignorado durante el desarrollo de la nueva oleada de tecnologías ASP.NET Core, también conocida como “ASP.NET vNext” (aunque queda poco tiempo para que se le pueda seguir llamando así ;D)

Y la verdad es que había un poco de inquietud al respecto porque todos los desarrollos y ejemplos publicados hasta la fecha de la nueva pila de de tecnologías para la web de Microsoft utilizaban exclusivamente C#, no había ni rastro VB. Esta sensación, además, se veía reforzada por la publicación de comentarios y artículos donde el futuro de Visual Basic en este entorno no parecía estar demasiado claro.
martes, 4 de marzo de 2014

El operador “?.”, también llamado safe navigation operator, era la característica más demandada para el lenguaje C# en Uservoice, uno de los principales canales utilizado por Microsoft para obtener feedback e ideas a aplicar en nuevas versiones de sus productos, y parece que definitivamente se está considerando la posibilidad de incluir este útil azucarillo sintáctico tanto en C# como en VB.

Para los que no lo conozcáis, se trata de una construcción que permite acceder a propiedades de objetos sin temor a las null reference exceptions lanzadas cuando estos objetos son nulos.
martes, 31 de mayo de 2011
Desde siempre, C# ha sido el lenguaje por excelencia del framework ASP.NET MVC, y por esta razón es bastante difícil encontrar en la web ejemplos escritos en otros lenguajes, como el popular Visual Basic .NET.

En el caso concreto de la capa Vista, prácticamente nadie escribe ejemplos utilizando Razor y VB.NET, por lo que los desarrolladores que siguen optando por este lenguage para trabajar sobre ASP.NET MVC (e incluso WebPages) lo tienen más complicado para entender y utilizar código existente. Además, a diferencia de lo que podría pensarse, la codificación no es exactamente igual en ambos lenguajes, y a veces no es fácilmente inferible, lo cual añade además un poco de dificultad al usar VB.

En este post vamos a mostrar una tabla de equivalencias entre C# y VB.NET a la hora de codificar distintas construcciones que utilizamos frecuentemente al crear vistas con Razor.

Archivos de vistas o páginas Razor

C#VB.NET
nombrearchivo.cshtmlnombrearchivo.vbhtml

Definición del tipo de datos del Modelo

C#VB.NET
@model Persona@ModelType Persona

Importación de espacios de nombres

C#VB.NET
@using MyApp.Models@Imports MyApp.Models

Definición de clase base de la vista

C#VB.NET
@inherits ClaseBase@Inherits ClaseBase

Bloque de código

C#VB.NET
@{
   // Código C#
}
@Code
   ' Código VB.NET
End Code

Instrucciones de bloque (*)

C#VB.NET
@if(a > b) {
   // Hacer algo
}
@If a > b Then
   ' Hacer algo
End If
(*) aplicable para instrucciones como if, while, for, foreach, using, switch, etc.

Salida de expresión

C#VB.NET
Hola, @Model.NombreHola, @Model.Nombre

Mezcla de código y marcado

C#VB.NET
@if(a > b) { 
   <p>A es mayor que B</p>
}
@If a > b Then
   @<p>A es mayor que B</p>
End If
@if(a > b) {
   @: una línea de texto o HTML
}
@If a > b Then
   @: una línea de texto o HTML
End If
@if(a > b) {
   <text>
      Aquí va texto o HTML
   </text>
}
@If a > b Then
   @<text>
      Aquí va texto o HTML
   </text>
End If

Definición de secciones de un Layout

C#VB.NET
@section Encabezado {
   <h3>Este es el encabezado</h3>
} 
@Section Encabezado
   <h3>Este es el encabezado</h3>
End Section

Creación de helpers

C#VB.NET
@helper Tabla(int num) {
   <ul>
   @for (int i = 1; i < 11; i++)
   {
      <li>@num x @i = @(num*i) </li>
   }
   </ul>
}
@Helper Tabla(num As Integer)
   @:<ul>
   For i = 1 To 10
      @<li>@num x @i = @(num * i) </li>
   Next
   @:</ul>
End Helper

Bloques de funciones

C#VB.NET
@functions {
    int suma(int a, int b)
    {
        return a + b;
    }
}
@Functions
    Function suma(a As Integer,
                  b As Integer) As Integer
        Return a + b
    End Function
End Functions

Razor templated delegates

C#VB.NET
@{
    Func<dynamic, object> bold =
        @<strong>@item</strong>;
}

Uso: @bold("Esto en negrilla")
@Code
   Dim bold =
      Function(item As Object)
         @<strong>@item</strong>
      End Function
End code

Uso: @bold("Esto en negrilla")
@helper Lista(
   Func<dynamic, HelperResult> templ,
   params dynamic[] args)
{
    foreach(dynamic item in args)
    {
        @templ(item)
    }
}

Uso:
<ul>
   @Lista(@<li>@item</li>,
          1, 2, 3.5, "hola",
          DateTime.Now)
</ul>
@Helper Lista(templ
    As Func(Of Object, HelperResult),
    ParamArray args() As Object)

   For Each item In args
      @templ(item)
   Next
End helper

Uso:
<ul>
   @Lista(Function(item As Object)
            @<li>@item</li>
          End Function,
          1, 2, 3.5, "hola",
          DateTime.Now)
</ul>

Espero que no se me haya quedado por detrás ninguna de las construcciones habituales. De todas formas, si detectáis alguna ausencia, no dudéis en avisarme y lo incluyo en este mismo post.

Publicado en: Variable not found.
lunes, 31 de mayo de 2010
.NET Los parámetros opcionales son una interesante ayuda que hace tiempo que está presente en otros lenguajes como Visual Basic .NET, y ha sido introducida en la versión 4.0 de C#, para alegría de muchos.

A grandes rasgos, esta característica nos permite especificar valores por defecto para los parámetros de nuestros métodos, ahorrándonos tiempo de codificación:

class Program
{
    public static void Main(string[] args)
    {
        Muestra();            // Imprime 1,1
        Muestra(8);           // Imprime 8,1
        Muestra(3,4);         // Imprime 3,4
        Console.ReadKey();
    }
 
    static void Muestra(int a=1, int b=1)
    {
        Console.WriteLine(a + "," + b);
    }
}

Desde siempre, ignorante de mí, había pensado que esto no era más que una triquiñuela del compilador, un azucarillo sintáctico destinado a facilitarnos la creación de sobrecargas de forma rápida, pero, ahora que lo he estudiado algo más a fondo, resulta que no es así. De hecho, los parámetros opcionales están soportados a nivel de plataforma, y funcionan de forma algo extraña (o al menos diferente a lo que podía esperarse), por lo que es conveniente conocerlos bien para no cometer determinados errores.

En primer lugar, me ha llamado la atención que la detección de la ausencia de parámetros en la llamada y la asignación de los valores por defecto no la realiza el método en el que se han definido. Es decir, sobre el ejemplo anterior, no es el método Muestra() el que comprueba si se han suministrado valores para los parámetros a y b, ni el que le asigna los valores por defecto en caso contrario. Esta "comprobación" se realiza en tiempo de compilación (!).

Esto lo demostramos muy fácilmente si descompilamos esta misma aplicación con ayuda del imprescindible Reflector, que nos mostrará el siguiente código:

class Program
{
    public static void Main(string[] args)
    {
        Muestra(1, 1);
        Muestra(8, 1);
        Muestra(3, 4);
        Console.ReadKey();
    }
 
    public static void Muestra([Optional, DefaultParameterValue(1)] int a, 
     [Optional, DefaultParameterValue(1)] int b)
    {
        Console.WriteLine(a + ", " + b);
    }
}

Como se puede observar, se ha generado un método Muestra() cuyos parámetros incluyen atributos que indican su opcionalidad y el valor por defecto en cada caso.

Pero lo curioso está en el método Main(), desde donde se hacen las llamadas, el que podemos observar que las invocaciones a Muestra() incluyen valores para todos los parámetros, como si se tratara de constantes especificadas directamente por el desarrollador.

Por tanto, no hay nada mágico en los métodos con parámetros opcionales, ni sobrecargas, ni código de comprobación o asignación insertado de forma automática. Es el propio compilador el que, en el momento de generar el código IL, extrae los valores por defecto de los parámetros no especificados en la llamada examinando los atributos presentes en la signatura y los introduce en la invocación.

Y es aquí justo donde hay que tener cuidado al utilizar los parámetros opcionales. Dado que el valor de los parámetros se determina en tiempo de compilación y se incluyen como constantes en el código IL generado, pueden producirse efectos no deseados si trabajamos con distintos ensamblados.

Veámoslo con un ejemplo, que, aunque poco real, creo que ilustrará un escenario donde los parámetros opcionales podrían jugarnos una mala pasada.

En el siguiente código, perteneciente al ensamblado LogicaNegocio.dll, vemos un método CalculaImporteconIva(), que retorna un importe aplicándole el impuesto (IVA) correspondiente:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.16)
    {
        return importe + importe*iva;
    }
}

Así, podemos tener un ensamblado externo, pongamos ERP.exe, que haga uso de este método de la siguiente forma:

public void muestraDesglose(double importe)
{
    double importeTotal = logica.CalculaImporteConIVA(importe)
    // Mostrarlo...
}

En el momento de compilación de ERP.exe, la llamada anterior quedará en el ensamblado resultante igual que si hubiéramos hecho esta llamada:
 
    double importeTotal = logica.CalculaImporteConIVA(importe, 0.16)

Si ahora se produce una subida de IVA (como lamentablemente va a ocurrir en breve), acudiríamos a modificar el valor por defecto del parámetro iva en el método CalculaImporteConIva() y recompilaríamos LogicaNegocio.dll:

public class LogicaNegocio
{
    public double CalculaImporteConIva(double importe, double iva = 0.18)
    {
        return importe + importe*iva;
    }
}

Sin embargo, si no recompilamos ERP.EXE desde éste seguiríamos enviándole el valor anterior (0.16, recordad que este valor aparece como constante en el ensamblado), lo que podía provocar algún problema. Es decir, si queremos mantener la coherencia del sistema, nos veríamos obligados a recompilar todos los ensamblados que referencien LogicaNegocio.dll.

Conclusión: en métodos públicos, y especialmente en aquellos que serán consumidos desde ensamblados externos, es conveniente utilizar parámetros opcionales sólo cuando los valores constantes sean “verdades universales”, como las constantes matemáticas o datos que con toda seguridad no van a cambiar.  No es buena idea utilizarlos para reflejar valores variables o constantes de lógica de negocio, con posibilidades de cambio aunque sean remotas.

Por último, comentar que aunque este post está centrado en C#, todas estas precauciones son igualmente válidas para Visual Basic .NET.

Publicado en: Variable not found
miércoles, 21 de abril de 2010
¿Sómos los primeros en llegar al estadio? Casualmente encuentro en el post de Chris Eargle “Any() versus Count()” un tema del que pensaba escribir hace tiempo y al final dejé en el tintero: ¿cómo podemos determinar si una enumeración está vacía? Vale, es bien fácil, una enumeración está vacía si tiene cero elementos.

Si trabajamos con un array, podemos consultar la propiedad Length; si se trata de una colección, podemos utilizar la propiedad Count, que también nos devolverá el mismo dato de forma directa.

Sin embargo, cuando procesamos información es frecuente tratar con tipos de datos enumerables cuya implementación exacta desconocemos, y en los que no tenemos acceso a ninguna propiedad que nos permita conocer el número de elementos exactos que tiene almacenados. Por ejemplo, esto ocurre cuando obtenemos un conjunto de datos en forma de IEnumerable o trabajamos con LINQ sobre alguna fuente de información.

Pues bien, en estos casos hay que ser algo prudentes con la forma de consultar el número de elementos. Me he topado con código propio en el que, simplemente por despiste, utilizaba el método Count(), facilitado por LINQ para las enumeraciones y otras colecciones de datos, con objeto de saber si una lista estaba vacía:

var prods = services.GetProductsByCategory(category);
if (prods.Count() > 0)
{
   // Hacer algo con los elementos de la lista
}
else
{
   // No hay elementos
}

Seguro que ya os habréis percatado de que eso está mal, muy mal. El método Count() recorrerá uno por uno los elementos para contarlos, retornando el número exacto de ellos. En escenarios de un gran número de elementos, o cuando es costoso obtener cada elemento (como al traerlos de una base de datos) puede suponer un consumo de recursos enorme.

Y justo por esto existe el método Any(), que comprueba únicamente si existe al menos un elemento en la colección. Internamente este método itera sobre la colección retornando true cuando encuentra el primer elemento, por lo que es mucho más eficiente que el anterior:

var prods = services.GetProductsByCategory(category);
if (prods.Any()) // Mucho mejor :-)
{
  // Hacer algo con los elementos de la lista
}
else
{
  // No hay elementos
}

La utilización de Any() también es interesante para comprobar la existencia de elementos que cumplan una condición, expresada en forma de predicado; retornará true cuando encuentre el primer elemento que lo haga cierto:

if (pedidos.Any(p => p.Pendiente))
{
   // Mostrar alerta, hay al menos un pedido pendiente
}

Estamos en la puerta de un Estadio y queremos saber si vamos a ser los primeros en entrar al recinto… ¿le preguntaríamos al portero el número exacto de aficionados que ya están dentro para, si es cero, saber que somos los primeros? ¿O nos bastaría simplemente con preguntarle si hay alguien? ;-P

Publicado en: Variable not found
Hey: ¡estoy en Twitter!
miércoles, 4 de noviembre de 2009

Cadenas Encuentro en el blog de Gunnar Peipman un post sobre el nuevo método string.IsNullOrWhiteSpace, aparecido en .NET Framework 4.0 Beta 2, cuya misión es retornar un booleano indicando si la cadena pasada como parámetro contiene un nulo, está vacío, o exclusivamente caracteres de espaciado (espacios, saltos de línea, tabulaciones, etc.), un escenario bastante frecuente cuando estamos, por ejemplo, validando formularios o cualquier tipo de datos de entrada.

Por si no podemos esperar hasta la llegada de la nueva versión del framework, Gunnar propone una solución temporal basada en crear un método de extensión sobre la clase string que realice la misma tarea:

 
public static class StringHelper
{
    public static bool IsNullOrWhiteSpace(this string s)
    {
        if (s == null)
            return true;
 
        return (s.Trim() == string.Empty);
    }
}
 
Como ya comenté por aquí hace tiempo, la ventaja que tiene utilizar esta técnica es que permite invocar el método sobre una instancia de cadena, aunque ésta sea nula, sin riesgo a errores:
 
string a = null;
string b = "    ";
string c = "\n";
string d = "Hey!";
Console.Write (a.IsNullOrWhiteSpace()); // True
Console.Write (b.IsNullOrWhiteSpace()); // True
Console.Write (c.IsNullOrWhiteSpace()); // True
Console.Write (d.IsNullOrWhiteSpace()); // False

Y para los fieles a Visual Basic .NET, ahí va el código equivalente:

Imports System.Runtime.CompilerServices
Public Module StringHelper
    <Extension()> _
    Public Function IsNullOrWhiteSpace(ByVal s As String) As Boolean
        If s Is Nothing Then
            Return True
        End If
        Return s.Trim() = String.Empty
    End Function
End Module

Publicado en: Variable not found.

domingo, 28 de diciembre de 2008
Charla de Matt Gretz sobre el futuro de VB.NETTras algunos días de intenso debate, ayer quedó constituida SVBNet, la plataforma pro-salvación de Visual Basic, como movimiento independiente de la comunidad de desarrolladores de habla hispana aupa.net, cuyo objetivo es apoyar la continuidad del lenguaje y hacer presión para forzar un replanteamiento de su estrategia comercial.

Para el que todavía no esté al tanto de la historia, durante la segunda semana de diciembre ha tenido lugar en Dallas el encuentro de desarrolladores DevConn4, en el que Matt Gretz, destacado miembro del equipo de VB.NET, hacía público el Roadmap que Microsoft tiene previsto para este producto, que no trae buenas noticias para la gran comunidad de desarrolladores en Visual Basic, y que provocó un revuelo impresionante tanto en la sala del evento como en la blogosfera y medios especializados.

Resumidamente, el Roadmap prevé la progresiva desaparición de Visual Basic, mediante un plan de migración que facilitará los desarrolladores pasar a C# en un plazo de tres años. A partir de 2012 no se publicarán nuevas versiones de VB, por lo que, dado que el lenguaje no evolucionará para reflejar los cambios que sean introducidos a nivel de framework, lo llevará irremediablemente a su desaparición en no más de cinco años.

En líneas generales, el contexto actual y el panorama que nos espera, si nadie lo impide, es el siguiente:
  • Primero, durante años se han ido introduciendo mejoras en Visual Basic .NET hasta cubrir la mayoría de características del framework e igualarlo en potencia a C#, con objeto de hacer más suave el salto de un lenguaje a otro. Por ejemplo, la inclusión en el actual VB.NET 9 de tal cantidad de novedades y cambios respecto a versiones anteriores, responden claramente a estas necesidades.

    Hasta aquí, todo es correcto y aporta exclusivamente ventajas. El problema viene ahora.
     

  • La segunda gran acción podremos verla con el próximo Visual Studio 2010 y .Net 4.0, de los cuales ya es posible descargar previews. Para entonces, Visual Basic aparecerá con el nombre comercial VB# (VB Sharp), dando a entender que se trata de una versión de transición a C#, y en el que podremos encontrar el giro hacia una sintaxis más cercana a este lenguaje, como el uso de llaves en bloques (if, blucles while y for, etc.), estructuras compactas (como la sintaxis lambda o declaraciones menos verbosas), comentarios tipo C "/*", y otras lindezas que seguro que dejarán nuestro código VB bastante diferente al actual. Eso sí, para facilitarnos la vida, será el propio IDE el que nos ayude a acostumbrarnos a las particularidades sintácticas, sustituyendo sobre la marcha el código VB.NET tradicional por VB#.

    En esta fase también encontraremos los cambios que se van a producir en C# para dotarlo de algunas de las ventajas de Visual Basic, como los parámetros opcionales, con objeto de lograr una mayor confluencia y hacer más fácil la adopción del lenguaje por esta comunidad de desarrolladores.
     

  • La última parte del plan se llevará a cabo en 2012 con Visual Studio (codename Moonwalker), que se distribuirá con .NET framework 5.0 (aka Greengarden) y no vendrá acompañado de una nueva versión de Visual Basic, sino de un asistente de migración a C#. Para esas fechas, además, se prevé que la mayoría de desarrolladores de Visual Basic haya adoptado VB#, por lo que el salto será, en palabras de Matt, "no traumático".

Roadmap de VB.NET presentado en DevConn4

Finalmente, comentar que también hemos acordado el emblema de la plataforma pro-salvación de Visual Basic que podemos ir utilizando en nuestras páginas para dar difusión al movimiento hasta que tengamos lista la web oficial, que se prevé finalizada para primeros de año.

Logo Plataforma pro-salvación de Visual BasicSi eres simpatizante de la causa, programas o has programado en alguna versión de Visual Basic, y tienes blog o una página relacionada con el desarrollo, descárgate esta imagen (¡por favor, no hagas hotlinking!) y colócala en un lugar visible apuntando al futuro sitio web de la plataforma. Si el diseño no encaja en tu web, no te preocupes: en breve tendremos a nuestra disposición nuevos botones en tamaños menores (125 y 250 píxeles de ancho) y con ciertas variaciones estéticas y nuevos colores, e incluso versiones en Flash y Silverlight, según Raúl Mondo, el diseñador que está realizando este excelente trabajo.

Y no olvides que cuanto más difundamos esta iniciativa, más fuerza tendremos ante la multinacional para hacer que se replantee su estrategia.

¡Larga vida a VB.NET!

[Actualizado 29/12]
Obviamente la noticia no es real, se trata simplemente de una broma del Día de los Inocentes, pero en cualquier caso... ¡Larga vida a VB! :-D


Publicado en: www.variablenotfound.com.
martes, 2 de diciembre de 2008
Código fuenteAl escribir el post "Métodos genéricos en C#", 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í.

Esto, unido a un comentario de Julio sobre el propio post en el que preguntaba si existía algo parecido en Visual Basic .NET, ha hecho que reedite el mismo, pero centrándome esta vez en dicho 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 obligatoriamenter 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 estabamos 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:
  • As Structure, indica que el argumento debe ser un tipo valor.
  • As Class, indica que T debe ser un tipo referencia.
  • As New, fuerza a que el tipo T disponga de un constructor público sin parámetros; es útil cuando desde dentro del método se pretende instanciar un objeto del mismo.
  • As nombredeclase, indica que el argumento debe heredar o ser de dicho tipo.
  • As nombredeinterfaz, el argumento deberá implementar el interfaz indicado.
  • As nombredetipogenérico, indica que el argumento al que se aplica debe ser igual o heredar del tipo, también argumento del método, indicado por nombredetipogenérico (observad en el siguiente ejemplo el parámetro T2).
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 las últimas incorporaciones al framework, y a la que hay que habituarse para poder trabajar eficientemente con ellas.

Publicado en: www.variablenotfound.com.