Archivo de la etiqueta: novedades

Nuevas ‘versiones’ de Visual Studio 2019

Pues eso… con fecha de anteayer 2 de marzo, hay nuevas versiones de Visual Studio 2019, tanto para la versión final: v16.9.0 como para la preview: v16.10 Preview 1.0.

Este es un extracto de las novedades de .NET Productivity de la versión 16.9.0:
He resaltado las que, a mí me, parecen más interesantes.

.NET Productivity (versión final v16.9.0)

  • There is now IntelliSense completion for preprocessor symbols.
  • Solution Explorer now displays the new .NET 5.0 Source Generators.
  • Go To All won’t display duplicate results across netcoreapp3.1 and netcoreapp2.0.
  • Quick Info now displays compiler warning IDs or numbers for suppressions.
  • Using directives will now automatically get added when copying and pasting types to a new file.
  • IntelliSense completion will now automatically insert a semicolon as a commit character for object creation and method completion.
  • Semantic colorization for C# 9.0 records.
  • Refactoring that removes unnecessary discards.
  • Refactoring that converts a verbatim and regular string to an interpolated string preserving curly braces that were intended to go in the output.
  • Code fix in Visual Basic that removes the shared keyword when you convert methods that are shared to a module.
  • A refactoring that suggests using new(…) in non-contentious scenarios
  • A code fix that removes redundant equality expressions for both C# and Visual Basic
  • The .NET Code Style (IDE) analyzers can now be enforced on build
  • The Syntax Visualizer shows the current foreground color for enhanced colors
  • A new tooltip when hovering over the diagnostic ID for pragma warnings
  • When you type the return key from within a comment the new line is now automatically commented out
  • Inline parameter name hints enhancements
  • .NET Core Debugging with WSL 2

Para la versión preview… pues… mejor lo miras en la página de información publicada por la gente de Microsoft.

Nos vemos.
Guillermo

Novedades de C# 9.0

Pues eso… tal como te dije hace unos días, ya ha llegado el momento de explicarte algunas de las novedades de C# 9.0; concretamente aquí te voy explicar de forma concisa (sin enrollarme mucho) en qué consisten tres de las novedades que trae la versión 9.0 de C#, la que se incluye en .NET 5.0 que en este mes de noviembre estará en modo release o, dicho de otro modo, en versión final.

NOTA:
En realidad hoy día 10 de noviembre lo han publicado, mientras escribía este artículo se hacía la presentación oficial de .NET 5.0 en el .NET Conf 2020 (que puedes ver la grabación usando el enlace).

Estas novedades que te voy a explicar aquí están basadas en la documentación de .NET, concretamente en: Novedades de C# 9.0.
Y las tres novedades que he elegido son las siguientes:
1- Instrucciones de nivel superior (Top-level statements)
2- Registros (Records)
3- Establecedores de solo inicialización (Init only setters)

NOTA:
No me voy a explayar (alargar demasiado) en las explicaciones, si quieres detalles, por favor mira el enlace que te he puesto antes y en la documentación te lo explican con gran lujo de detalles.

Para usar el compilador de C# 9.0 necesitarás tener instalado .NET 5.0 y el código ejecutarlo con Visual Studio 2019 Preview o usando dotnet desde la línea de comandos o con Visual Studio Code.

Instrucciones de nivel superior (top-level statements)

Esta novedad es solo aplicable a un fichero en cada proyecto de C# y viene a ser una forma más simple de escribir el punto de entrada de una aplicación que normalmente se define con el método estático Main. De hecho, si se utiliza esta forma de escribir el código no podrá existir otro punto de entrada diferente, es decir, no podrá haber otro método estático Main que indique por dónde empezar a ejecutar el código.

En cualquier programa de C# nos encontraremos con un código parecido a este (por ejemplo el clásico Hola Mundo:

using System;

namespace novedadescs9_01
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Pero ahora con C# 9.0 lo podemos simplificar de esta forma:

using System;

Console.WriteLine("Hello World!");

Es decir, nos quedamos con el código realmente operativo. En este caso la importación de System para poder acceder al método WriteLine de la clase Console.

Como te comenté antes, esta forma de escribir el código solo se puede hacer en un fichero del proyecto y equivale al método de entrada de dicho proyecto.

Si ese método debe manipular los argumentos de la línea de comando, estos se pueden seguir gestionando con args (aunque no estén indicados en ningún sitio).

Por ejemplo, supongamos que queremos tomar el primer argumento como el nombre al que saludar, lo haríamos de esta forma:

using System;

Console.WriteLine("¡Hola {0}!", args[0]);

De la misma forma, si nuestro método Main (aunque no haya aparentemente ninguno) tiene que devolver un valor, por ejemplo que devuelva 1 si no se ha indicado nada en la línea de comandos o lo indicado está vacío, lo haríamos de la siguiente forma:

using System;

if (args.Length == 0 || string.IsNullOrEmpty(args[0]) )
    return 1;

Console.WriteLine("¡Hola {0}!", args[0]);

return 0;

Si ejecutas el código anterior sin indicar nada (ningún argumento) no hará (aparentemente) nada, pero en realidad si hace, ya que devuelve 1 como resultado de la ejecución. Si se indica algo mostrará el mensaje de saludo y devolverá cero.

Para hacerlo más evidente, podemos cambiar el if de la siguiente forma:

using System;

if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
{
    Console.WriteLine("Debes escribir un nombre.");
    return 1;
}

Console.WriteLine("¡Hola {0}!", args[0]);

return 0;

Ahora estará más claro que no hemos escrito nada como argumento al ejecutar el programa.

NOTA:
Si has elegido usar dotnet puedes hacer las pruebas de la siguientes formas:
(se supone que estás usando la terminal de Visual Studio Code o el símbolo del sistema con el directorio activo donde esté el código.

Si indicas un argumento, será así:
dotnet run Guille
y el resultado será:
¡Hola Guille!

Si no indicas argumentos escribe esto:
dotnet run
y el resultado será:
Debes escribir un nombre.

En la figura 1 puedes ver la salida de las dos formas de usarlo (con o sin argumento).

Figura 1.

 

Registros (Records)

Para escribir el código de la segunda novedad de C# 9.0 vamos a crear un nuevo proyecto, si quieres hacerlo con dotnet desde la línea de comandos, sitúate en la carpeta donde dotnet creará una carpeta con el código necesario para este ejemplo el cual se llamará novedadescs9_02 y escribe lo siguiente:

dotnet new console -o novedadescs9_02

Cambia al directorio recién creado con cd novedadescs9_02 y edita el fichero Program.cs para que tenga el código que te mostraré a continuación, aunque antes un poco de explicación sobre de qué va esto de los registros o records.

Los tipos de registro (record types) es un tipo por referencia que son inmutables de forma predeterminada. Inmutable significa que una vez creados no se pueden modificar.

Hasta ahora los tipos por referencia (clases y tipos anónimos) no contenían los datos que manipulaban si no una referencia a esos datos, mientras que los tipos por valor (entre ellos las estructuras y tuplas) contienen los valores, es decir, si pasamos un tipo por valor a un método se pasa una copia de los datos, a diferencia de los tipos por referencia que pasan una referencia al objeto en memoria que contiene los valores.
Y esto es principalmente lo que hacen los tipos de registro cuando se pasan a un método, no se pasa la referencia, si no una copia de los datos.

De hecho este es uno de los puntos importantes de los tipos record, que al ser inmutables se pueden comparar de la misma forma que comparamos, por ejemplo un tipo entero, y devolverá un valor de igualdad si todos los campos/propiedades que contiene son iguales. Algo que con las clases no ocurre.

Veamos una declaración de un tipo record de la forma más simple (ahora veremos cómo definirlo de una forma parecida a una clase.

NOTA:
Para simplificar voy a usar el código tal y como te he explicado en el primer punto de este artículo como instrucciones de nivel superior (top-level statements).

public record Persona(string Nombre, int Edad);

Esta sería la forma simplificada de definir un registro.
En este caso usamos la palabra clave record y en este caso define un tipo de registro con dos propiedades: Nombre de tipo cadena y Edad de tipo entero.

Para usarla lo haremos de la misma forma que con otros tipos:

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine(persona1);

Fíjate que al mostrar el contenido de la variable persona1 se muestra de una forma ya predefinida, pero como siempre, al forma de actuar de ToString (que es el que se encarga de mostrarlo de esa forma), lo puedes definir a tu antojo.
En ese ejemplo la salida será la siguiente:

Persona { Nombre = Guillermo, Edad = 63 }

Si queremos definir un método ToString personalizado lo haremos de la forma tradicional (ahora tenemos que usar llaves para definir el método):

public record Persona2(string Nombre, int Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad}";
    }
}

Por supuesto también podemos usar la herencia con los tipos record, imagina que el tipo Persona2 lo quieres derivar de Persona y añadir la definición de ToString, simplemente lo haríamos así:

public record Persona2(string Nombre, int Edad) : Persona (Nombre, Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad}";
    }
}

Aunque aquí no ganamos mucho, pero al menos definimos Persona2 a partir del tipo Persona.

Para ver una clase derivada que añada alguna funcionalidad extra, por ejemplo que tenga otra propiedad más podemos definir el tipo Personaje a partir del tipo Persona, en este caso, el tipo Personaje define una propiedad de tipo entero llamada Desde (además de las dos propiedades que expone el tipo Persona).

public record Personaje(string Nombre, int Edad, int Desde) : Persona(Nombre, Edad)
{
    public override string ToString()
    {
        return $"{Nombre} nació en {DateTime.Now.Year - Edad} y está activo desde {Desde}.";
    }
}

Aquí también definimos el método personalizado ToString, pero si no queremos definirlo, simplemente haríamos lo siguiente:

public record Personaje(string Nombre, int Edad, int Desde) 
                        : Persona(Nombre, Edad);

Al mostrar el contenido del personaje1 el resultado en este caso sería (obviamente) el predeterminado del método ToString.

En la figura 2 tienes la salida del código con la definición propia del método ToString y en la figura 3 la salida sin definir un método ToString para el tipo de registro Personaje.

 

Así que, ya es cosa tuya cómo quieres que se comporte el método ToString.

Por supuesto, puedes definir otros métodos que necesites. Y ya sabes cómo 😉

¿Qué significa que los tipos record son inmutables?

Como te comenté antes, las propiedades definidas en un tipo de registro no se pueden cambiar, son inmutables (como las cadenas de .NET), por tanto si queremos cambiar el valor de una propiedad de un tipo ya en memoria, simplemente NO PODEMOS HACERLO. De hecho, de hacerlo lo que conseguiríamos es crear un nuevo registro con los nuevos valores. Pero vayamos por pasos.
Si tienes la definición de Persona que hemos visto anteriormente y decides cambiar la edad de la persona1 simplemente no podrás, ya que te indicará que Edad es de solo lectura.

Escribe lo siguiente (pongo el código completo para que te resulte más fácil hacer la prueba):

using System;

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine(persona1);
persona1.Edad = 64;
Console.WriteLine(persona1);

public record Persona(string Nombre, int Edad);

Si intentas ejecutar esto desde la línea de comandos con dotnet te mostrará el siguiente error (ver la figura 4):

Figura 4. Error de compilación usando dotnet.

Si ese código lo escribes en Visual Studio 2019 Preview, tal como puedes ver en la figura 5, te mostrará la asignación (lo que te muestro en rojo en el código anterior) como un error que indica que:

Error CS8852: Init-only property or indexer 'Persona.Edad' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
Figura 5. El error tal como lo muestra Visual Studio 2019 Preview.

Ese mensaje nos lo aclarará la siguiente y última sección de este artículo sobre las novedades de C# 9.0. Pero eso dentro de un momento.

var persona2 = persona1 with { Edad = 64 };

Console.WriteLine("La persona2: {0}.",persona2);

Aquí lo que hemos hecho es crear una copia de persona1 con un nuevo valor en la edad, pero realmente son dos objetos en memoria diferentes: Uno con la edad de 63 y el otro con la edad de 64, es decir: ¡hemos clonado al guille con un año más! (de seguir con otro ejemplo más, me jubilaré pronto jejeje).

Fíjate que no hemos usado el tipo Persona para crear el nuevo objeto, simplemente lo hemos creado a partir del que ya estaba definido en persona1, pero con algunos datos diferentes, en este ejemplo simplemente hemos cambiado el valor de Edad.

Ahora veremos lo que te comentaba al principio de la forma de comparar objetos creados a partir de tipos de registro.

Veamos el siguiente código en el que definimos dos objetos del tipo Persona, en los que los hemos inicializado con los mismos datos y después asignamos a la variable iguales el resultado de comparar con == ambos objetos.
como veremos al ejecutar el código es que ambos son iguales, ¡aunque en realidad sean dos objetos diferentes!

var persona1 = new Persona("Guillermo", 63);
Console.WriteLine("La persona1: {0}.", persona1);

var persona2 = new Persona("Guillermo", 63);
Console.WriteLine("La persona2: {0}.", persona2);

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);


public record Persona(string Nombre, int Edad);

El resultado de ejecutar ese código será el siguiente:

La persona1: Persona { Nombre = Guillermo, Edad = 63 }.
La persona2: Persona { Nombre = Guillermo, Edad = 63 }.
persona1 == persona2 es True

Esa comparación de igualdad la podemos escribir de la siguiente forma:

var iguales = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales);

El resultado será el mismo que usando el operador de igualdad.

Y para finalizar este apartado de los tipos de registro veamos qué ocurre si tenemos el siguiente código:

var persona2 = persona1 with { Edad = 64 };
Console.WriteLine("La persona2: {0}.", persona2);

var iguales = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales);

¿Cuál crees que será el valor de iguales?

La respuesta: tendrás que ejecutar el código para comprobarlo 😉

 

Deconstruir tipos de registro

Una cosa interesante con los tipos de registro (record) es lo que se conoce como de-constructor (Deconstruct) (mira la documentación para una explicación detallada) ya que aquí solo te pondré un ejemplo para que sepas que se puede hacer algo como esto:

var persona = new Persona("Guillermo", 63);
Console.WriteLine("La persona: {0}.", persona);

var (n, e) = persona;
Console.WriteLine($"{n} {e}");


public record Persona(string Nombre, int Edad);

Es decir, podemos asignar a una tupla el contenido de un registro, en este ejemplo (tal como puedes comprobar al ejecutar el código) es que se asignan los valores del Nombre y la Edad a la tupla.

 

 

Establecedores de solo inicialización (Init only setters)

Tal como vimos en el penúltimo ejemplo, o mejor dicho en el mensaje de error del penúltimo ejemplo (al que asignamos un valor a la propiedad Edad de un tipo de registro), el mensaje de error indica que las propiedades de solo inicialización solo se pueden asignar en un inicializador de objeto.
Es decir, la propiedad Edad es de solo lectura, pero en este caso, el error nos indica que la propiedad de solo inicialización (init-only property) solo se puede usar en un inicializador de objetos. Y aquí es donde entra esta tercera novedad de C# 9.0 que te quiero comentar, pero veamos primero un código de ejemplo de cómo usar esta nueva instrucción: init.

class Persona
{ 
    public string Nombre { get; init; }
    public int Edad { get; init; }
}

Como ves en el código anterior, en la definición de las propiedades de la clase Persona están los modificadores get e init. Get es, como bien supondrás, la parte que devuelve el valor de la propiedad (lo que hasta ahora hemos tenido) y en este caso init sustituye al modificador set de la propiedad; pero en este caso concreto le indicamos que esa asignación se hará solo y exclusivamente al iniciar una instancia de la clase Persona.

Una forma de definir un objeto persona1 a partir de la clase Persona sería la siguiente:

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };

Aquí estamos usando una clase en lugar de un tipo de registro. Por eso esa forma de crear el nuevo objeto. Lo he hecho así, para que no te olvides de cómo crear nuevos objetos a partir de una clase 😉

Como ves el funcionamiento es parecido a lo que hemos visto en la sección anterior, pero el definir una clase es porque los tipos de registro de forma predeterminada tienen el inicializador definido en las propiedades.

Pero el que los tipos de registro sean inmutables no quiere decir que no podamos definir propiedades de lectura/escritura, de hecho podemos hacer algo como esto:

record Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Con el código anterior creamos un record que permite cambiar el valor de la propiedad Nombre, por tanto podemos hacer algo como lo siguiente sin que nos de error:

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
Console.WriteLine(persona1);

persona1.Nombre = "Guille";
Console.WriteLine(persona1);

Y podemos hacerlo porque la propiedad Nombre ya no está definida como solo de inicialización.

Pero aún así, si definimos otro objeto del tipo (record) Persona con los mismos datos, la comparación de igualdad seguirá funcionando como vimos anteriormente.

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = new Persona { Nombre = "Guille", Edad = 63 };

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

En este caso, el segundo objeto (persona2) lo definimos con «Guille» como nombre, si no, no sería igual que el objeto persona1.

Y seguramente pensarás que ¿Para qué quiero el tipo record, si parece que funciona igual que si lo defino como class?

Bien pensado, pero no, los tipos de registro (record) no funcionan igual que los tipos definidos a partir de una definición de una clase (class), y para muestra un botón.

Veamos el ejemplo anterior usando una clase en lugar de un registro.

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = new Persona { Nombre = "Guille", Edad = 63 };

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Si ejecutas ese código (te recuerdo que Persona está declarado con class en lugar de record) el valor asignado a iguales será false.

Y es False porque en realidad son dos objetos por referencia que no usan las nuevas características de los tipos de registro, en el que la comparación de igualdad se hace comprobando los valores campo a campo (o propiedad a propiedad), mientras que en los tipos «clasicos» por referencia se comprueba si el objeto es el mismo, y en este caso, no son el mismo, cada variable hace referencia a un valor diferente en la memoria.

Otra cosa es que escribamos el siguiente código:

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = persona1;

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

var iguales2 = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales2);

class Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

En este caso, tanto el valor de iguales como el de iguales2 será True, ya que solo tenemos un objeto en la memoria, pero dos variables que hacen referencia al mismo objeto y por tanto, si cambiamos el valor de la propiedad Nombre en una de las variables ese cambio se hará efectivo en los dos objetos.

Si no me crees añade el siguiente código antes de la definición de la clase Persona y verás lo que muestra ahora.

Console.WriteLine("Antes de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

persona1.Nombre = "Guillermo";

Console.WriteLine("Después de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

Si ejecutas nuevamente el código con estos cambios, la salida será la siguiente:

Antes de la nueva asignación
Guille
Guille
Después de la nueva asignación
Guillermo
Guillermo

Esto demuestra que las dos variables están apuntando al mismo objeto en memoria.

Para terminar, veamos qué ocurre si en lugar de una clase usáramos un tipo record.

¿Te atreves a dar la respuesta a qué mostraría ese código?
No… o sí… bueno veamos el código y el resultado y así salimos de dudas 😉

using System;

var persona1 = new Persona { Nombre = "Guillermo", Edad = 63 };
persona1.Nombre = "Guille";

Console.WriteLine(persona1);

var persona2 = persona1;

var iguales = persona1 == persona2;
Console.WriteLine("persona1 == persona2 es {0}", iguales);

var iguales2 = persona1.Equals(persona2);
Console.WriteLine("persona1.Equals(persona2) es {0}", iguales2);

Console.WriteLine("Antes de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

persona1.Nombre = "Guillermo";

Console.WriteLine("Después de la nueva asignación");
Console.WriteLine(persona1.Nombre);
Console.WriteLine(persona2.Nombre);

record Persona
{
    public string Nombre { get; set; }
    public int Edad { get; init; }
}

Efectivamente, el resultado es como el anterior.

Ya que al hacer la asignación directamente:

var persona2 = persona1;

Estamos compartiendo la misma dirección de memoria en las dos variables, como puedes comprobar, esto no cambia con los tipos por referencia, sean clases o registros.

Y para terminar, solo comentarte que una asignación como esta:

var persona2 = persona1 with { Nombre = "Guille" };

Solo la podemos hacer con los tipos de registro (record) no con las clases (class).

Si lo intentamos con un tipo Persona definida como class el error sería:

Error CS8858: The receiver type 'Persona' is not a valid record type.

Y con esto te dejo por hoy… espero que te hayas aclarado un poco y si no es así… lo siento, pero no sé cómo explicarlo mejor… y si me surge cómo explicarlo mejor, no dudes que te lo explicaré… todo será cuestión de ir practicando con el nuevo tipo de C# 9.0.

En cualquier caso, como siempre, ¡Espero que te haya sido de utilidad! esa es siempre la intención 😉

Nos vemos.
Guillermo

El código fuente con todo el código usado en el artículo

Lo he publicado en github: Novedades de C# 9.0

Tips para crear proyectos de .NET Core (.NET 5.0) en Visual Studio 2019 Preview

Pues eso… leyendo el otro día el artículo Visual Basic support planned for .NET 5.0 (soporte planeado en .NET 5.0 para Visual Basic) indicaba una serie de tipos de proyectos, entre ellos de Windows Forms y WPF, pero no solo para Windows y usando el .NET Framework (que es lo que dicen por ahí que será lo que nos quede en un futuro a los que preferimos usar Visual Basic en lugar de C# (u otro lenguaje de .NET).

Así que… abrí el Visual Studio 2019 (Community) Preview (v16.8.0 Preview 2) y me puse a mirar los tipos de proyectos que había… para C# había de esos dos tipos de aplicaciones tanto para .NET Core como para .NET Framework, pero para Visual Basic solo era para este último marco de trabajo.

Mirando la configuración de los ficheros del proyecto de C# pude crear un proyecto de Windows Forms para Visual Basic que usa el .NET 5.0 basándome en uno de consola (de esos si que hay para el .NET Core o el .NET 5.0).

¡Y funciona!

El problema, es que al agregar los controles al formulario y crear los métodos de evento (haciendo doble-pulsación en el control), los métodos de evento se creaban, pero no estaban conectados con los controles. Y eso es porque al crear los controles (añadiéndolos al formulario) no se definían con WithEvents.

La solución es fácil, se abre el fichero Form1.Designer.vb, se modifica la declaración de los controles (que suelen estar al final de ese fichero) y asunto arreglado… bueno, si le añades el típico Handles después de la declaración del método, por ejemplo:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Label1.Text = "¡Hola Mundo!"
End Sub

Esto último es lo menos engorroso de hacer… lo complicado (o tedioso) es convertir ese proyecto de consola a uno de Windows Forms.
¡Y no te voy a explicar cómo hacerlo! 🙂
No, ya que no es necesario hacer nada si sigues el consejo que te daré a continuación.

Crear proyectos de Visual Basic para .NET Core (o .NET 5.0)

Si no quieres complicarte mucho la vida, haz lo siguiente (tal como se muestra en la figura 1):

Selecciona el menú de Herramientas>Opciones y se muestra la ventana de configuración, selecciona Entorno>Características en versión preliminar y ahí marca la casilla Mostrar todas las plantillas de .NET Core en el cuadro de diálogo nuevo proyecto y pulsa Aceptar. Tendrás que cerrar y abrir nuevamente el Visual Studio y ya tendrás todas las plantillas que hay actualmente disponibles.

 

Usar los nuevos tipos de proyectos

Si ahora creas un nuevo proyecto (o agregas uno a una solución existente) verás que ya están los proyectos de Visual Basic para todas las plataformas (usando Windows Forms y WPF entre otros).

Una vez que seleccionas uno de los proyectos para «todas las plataformas» te dará la opción de elegir la plataforma de destino (el «framework» que usarás). (ver la figura 3)

Si eliges una aplicación de WPF te mostrará las 3 opciones de la figura anterior, si eliges una aplicación de Windows Forms, solo mostrará .NET Core 3.1 y .NET 5.0.

 

NOTA:
Como sabrás .NET 5.0 es una especie de remix entre el .NET Framework y el .NET Core o lo que es lo mismo, es la continuidad de .NET Core, pero unificado con el .NET Framework.
Y la primera versión definitiva está planeada para noviembre de este año de 2020.

 

¿Se soluciona algo al crear así las aplicaciones de Windows Forms?

Pues no… o casi… Me refiero a que al añadir un control al formulario y hacer doble-clic en él se genere correctamente el método de evento.

Ni usando el .NET Core 3.1 ni usando el .NET 5.0 (al menos en Visual Basic) se generan correctamente esos métodos de evento.

Si usas .NET Core 3.1 como plataforma de destino, al menos se definirán los controles con WithEvents, pero tendrás que añadirle el Handles o enlazar el evento y el método con AddHandler.

Si usas como plataforma de destino el .NET 5.0, no se definen los controles con WithEvents y, por tanto, tampoco se crean los métodos con Handles ya que es un requisito el que las variables (controles en este caso) estén definidos con WithEvents para permitir definir los eventos con Handles.

 

A esperar toca…

Esperemos que en futuras revisiones del .NET 5.0 o de Visual Studio esté solucionado, si no… lo tenemos complicado.
No sé quién será el encargado de arreglar esto, supongo que los de VS, y esperemos que sea así, ya que el .NET 5.0 ha entrado en lo que llaman la fase “feature complete” (función completa) en la Preview 8 de hace una semana y ya solo nos quedan las release candidate en las que solo arreglan bugs, no añaden nuevas características.

Habrá que reportarlo como BUG para ver si hacen algo y lo solucionan… porque si no lo solucionan, es que realmente no quieren que los desarrolladores de Visual Basic sigamos usándolo… y… optemos por cambiar a C#… en fin…

 

Nota:
Aunque al crear los proyectos de Windows Forms y WPF aparentemente sean para todas las plataformas, en realidad solo están soportadas en Windows.
O al menos eso quiere decir (o es lo que yo entiendo que significa) esto en la configuración de la aplicación:

<TargetFramework>net5.0-windows</TargetFramework>

 

Espero que te haya servido para algo todo lo aquí comentado… ya sabes que esa es la idea 😉

Nos vemos.
Guillermo

Novedades (algunas) del IDE de Visual Studio 2019

Pues eso… a falta de una semana para que Microsoft libere la versión final de Visual Studio 2019 (el martes 2 de abril para ser concreto) te voy a explicar un par de cosillas que creo que debes saber para que no te pase lo que a mí… Winking smile

 

Lo primero, es lo primero… evitarte quebraderos de cabeza

Así que… y como te digo en el encabezado, si quieres evitarte quebraderos de cabeza nada más empezar con esta monería de Visual Studio (porque ha mejorado mucho con respecto al Visual Studio 2017), debes quitar una de las nuevas opciones que tiene esta versión.

Al menos si usas el VS2019 en un monitor grande (o de los que tienen mucha densidad de píxeles) lo primero que deberías hacer es ir al menú Herramientas (Tools), seleccionar Opciones (Options) y en la ventana que te muestra, en Entorno>General (Environmet>General) (ver la figura 1) quitar la marca de Optimizar la representación de las pantallas con densidades de píxeles distintas (Optimize rendering for screens with different pixel densities).

Una nueva opción de VS2019 que puede darte problemas... a mí me las ha dado...
Figura 1. Una nueva opción de VS2019 que puede darte problemas… a mí me las ha dado…

¿Por qué he quitado esa opción?

Te explico.

Yo uso el portátil (laptop) como equipo de desarrollo con un monitor grande con una resolución de 3840×2160 y ahí es donde uso el Visual Studio 2019 (y el VS2017) y al menos con la versión RC.3 (y también con la RC.4) me da problemas como ni siquiera poder cambiar el tamaño de los formularios y/o controles. Aparte que al mostrar las opciones del proyecto, la ventana se mostraba muy mal o cuando el cursor del ratón estaba sobre un texto y se muestra IntelliSense no dejaba escribir salvo que ocultaras la ventana emergente (de IntelliSense)… y más cosas de las que ahora mismo no me acuerdo, pero cuando me vengan a la memoria te las contaré…

Nota:

A ver… lo mismo tú no tienes los problemas que yo he tenido, solo te lo explico por si te pasan cosas raras cuando empieces a usar Visual Studio 2019.

El que avisa…

 

El coloreado del código de VB y C# ha cambiado

Esa es otra novedad con respecto a Visual Studio 2017 que nos puede facilitar la lectura del código (si quieres).

La figura 2 es una captura de Visual Studio 2017 con un código de Visual Basic. Este coloreado seguro que ya lo conoces.

El coloreado (clásico) de Visual Studio 2017
Figura 2. El coloreado (clásico) de Visual Studio 2017

En la figura 3 tienes la nueva versión del coloreado que trae Visual Studio 2019.

El coloreado (mejorado) de Visual Studio 2019
Figura 3. El coloreado (mejorado) de Visual Studio 2019

Pregunta: ¿No te gusta el nuevo coloreado de Visual Studio 2019?

Respuesta: Sí, me gusta, pero no me lo muestra así; lo veo como en la figura 2.

Esa característica la puedes cambiar en las opciones de configuración, concretamente en las opciones del editor de texto, ya sabes: Herramientas>Opciones y en la ventana de opciones selecciona Editor de Texto (Text Editor) y después Basic o C# (esta opción solo está disponible para esos dos lenguajes). A continuación marca la opción Usar colores mejorados para C# y Basic (Use enhanced colors for C# and VB) tal como puedes ver en la figura 4.

El coloreado mejorado del código se puede cambiar para usar el de las versiones anteriores
Figura 4. El coloreado mejorado del código se puede cambiar para usar el de las versiones anteriores

Nota:

Si te fijas en la figura 4, también hay opciones para colorear las expresiones regulares y otras mejoras.

 

Nueva pantalla de inicio de Visual Studio 2019

Para finalizar esta introducción a las mejoras (o cambios) del IDE de Visual Studio 2019 vamos a lo primero que nos mostrará Visual Studio al empezar: La pantalla de inicio.

La pantalla de inicio de Visual Studio 2019
Figura 5. La pantalla de inicio de Visual Studio 2019

Tal como puedes ver en la figura 5 la pantalla de inicio de VS 2019 ha cambiado, si no quieres cargar ningún proyecto, ni crear uno nuevo, puedes pulsar en el enlace mostrado en la parte inferior derecha: Continuar sin código (Continue without code).

Los proyectos mostrados están sincronizados con los que ya tuvieras en la ventana de inicio de Visual Studio 2017, incluso siguen sincronizados en los dos entornos, respetando también los que están anclados.

Lo único que no me gusta es la forma de crear nuevos proyectos, de eso te hablaré en otra ocasión. Además de que hecho en falta las noticias, que seguramente estarán en otra parte, pero por ahora no he dado con ellas, aparte de ir a el blog de los desarrolladores de Visual Studio.

 

Espero que te haya sido de utilidad. Esa es la idea (que no IDE) Winking smile

 

Nos vemos.
Guillermo

P.S.

Aquí te dejo unos enlaces, dos de ellos sobre lo primero que te he contado y que la gente de Visual Studio lo llama: Representación con reconocimiento del monitor (Per-monitor aware (PMA) rendering).

Si prefieres ver los artículos en inglés, al menos los publicados en docs.microsoft.com, puedes cambiar /es-es/ por /en-us/ (o viceversa) en la barra de direcciones.

Normalmente las traducciones al español son generadas automáticamente, pero mucho mejores que si las traduces con algunas herramientas de traducción, incluso la que incorpora Edge que se supone que es la misma que usa Microsoft en su sitio.

Las novedades de Visual Basic 15.5 y C# 7.3

Pues eso… esto es lo último que dicen en el sitio de Microsoft sobre las novedades de Visual Basic .NET y C#

En la página de Novedades de Visual Basic indica que es la versión 15.5 disponible en Visual Studio 2017 versión 15.5 (supongo que o superior, aunque en el Visual Studio Professional 2017 versión 15.9.2 las versiones de VB y C# son diferentes, al menos eso es lo que se muestra en la ventana Acerca de tal como puedes comprobar en la figura 1 donde se indica que ambas son la versión 2.10.0-beta2).

Versiones_csharp_vb_vs_pro_2017_15_9_2
Figura 1. Las versiones de VB y C# mostradas en Visual Studio 2017 v15.9.2

En cuanto a las Novedades de C# la última versión indicada en la página de Microsoft es C# 7.3 disponible en la versión 15.7 de Visual Studio 2017 y como se ve en la captura de la figura 1 (del Visual Studio Professional 2017 versión 15.9.2) la versión allí mostrada es la 2.10.0-beta2.

Si usas los ejecutables de compilación desde la línea de comandos (abre el acceso directo que te habrá creado la instalación de Visual Studio 2017 ya sea la Professional o la Community) tanto vbc como csc te muestran que es la versión 2.10.0.0.
Pero eso no es lo importante, lo que importan son las características indicadas en las páginas de novedades tanto de C# como de Visual Basic.

Espero que antes de que acabe este mes publique un artículo sobre cómo usar las «tuplas» (Tuples) tanto en Visual Basic como en C#, al menos en las incluidas en las versiones 7.2 de .NET), pero eso será otro día Winking smile

Nos vemos.
Guillermo