Novedades C# 12

26 Oct 2023 20 min (0) Comentarios

Como cada año por estas fechas, vamos a ver la lista de Novedades que nos trae la nueva versión de C#, en este caso las novedades de C# 12. Quedan aún un par de semanas para el “lanzamiento” oficial de la versión, pero todas las novedades están ya anunciadas, así que vamos a darles un vistazo. 

 

 

1 - Primary constructors de class y Struct

Vamos a empezar por los primary constructores, te puedes preguntar, qué es un primary constructor? Bueno si trabajas con records en C# sabrás que podemos indicar en la propia definición del record los “parámetros”, en vez de crear las propiedades/fields. 

 

Esto es básicamente un primary constructor, hasta ahora solo se podia en records y a partir de ahora se podrá también tanto en clases como structs

//Normal:
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

//Primary constructors:
public class User(string Name, string Email);

Este cambio parece una tontería pero yo lo uso siempre cuando trabajo con records, así que espero hacer lo mismo con clases y structs. Y ni decir tiene la cantidad de código que vamos a dejar de escribir gracias a esto. 

 

Por cierto, esto quiere decir que a partir de ahora tu clase no tendrá un constructor “vacío” por defecto. Y tener primary constructos no nos bloquea el crear más constructores u otra lógica dentro de la propia clase.

 

 

2 - Valores por defecto en las expresiones lambda

A partir de ahora vamos a tener la posibilidad de tener valores por defecto cuando definimos una lambda, vamos a imaginar que tenemos un delegado el cual recibe un valor y lo concatena en un string:

var buildString = (string s) => $"El valor es {s}";
string result = buildString("test");

Pues a partir de ahora, podemos poner en dicha lambda un valor por defecto:

var buildString = (string s = "test") => $"El valor es {s}";
string result = buildString();

 

El cambio está bien, es algo pequeño, o que de primeras podemos pensar que no tiene mucha importancia pero para mi si la tiene porque añade consistencia al lenguaje, si en un método normal puedes especificar valores por defecto, porque no ibas a poder en una lambda. 

 

 

3 - Alias de tipos

Desde hace mucho tiempo podemos utilizar un alias para una clase cuando tenemos otra en otro namespace que tiene el mismo nombre, Seguramente te haya pasado tener en el código algo así: 

using MyUser= namespace.dentro.de.mi.poryecto.User;

A esto se le denomina alias, pues a partir de C# 12 vamos a poder definir tipos de la misma manera, me explico, vamos a declarar un nombre, como hacemos ahora, y simplemente le indicamos un nombre y definimos las propiedades que va a tener:

using CustomUser = (string Nombre, string Apellido);
using UserIds = int[];

Como puedes ver, lo podemos ver, en este caso actúa como una tupla, y personalmente es el único caso de uso que le veo sentido. 

 

Alguna vez nos ha pasado que tenemos que crear una clase porque queremos pasar 3 o 4 argumentos a un método, pero la cabecera del método es muy grande o lo que sea, al final tiras por una tupla que copias tres o cuatro veces, cosas así. Pues con esto, se acaba ese problema, ya que solo la vas a definir una única vez. 

Personalmente pienso que teniendo primary constructors, una clase privada en la propia clase es más útil que esto, pero bueno, espero que esta funcionalidad no acabe siendo usada por todas partes, ya que en mi opinión puede dar pie a que sea un poco inmantenible. 

 

 

4 - Expresiones en colecciones

Llevamos unas cuantas versiones de C# que estamos teniendo avances en declarar colecciones y expresiones, en C# 11 vimos los patrones en listas, esos patrones se aplican principalmente con el operador spread, que vienen siendo los dos putos ...

 

Entre otros cambios pues ahora puedes coger y concatenar múltiples arrays utilizando dicho operador: 

int[] row0 = [1, 2, 3];
int[] row1 = [4, 5, 6];
int[] row2 = [7, 8, 9];
int[] single = [..row0, ..row1, ..row2];
foreach (var element in single)
{
    Console.Write($"{element}, ");
}
// Salida:
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 

Como hay muchas funcionalidades relacionadas con las colecciones, aquí te dejo el blog original de microsoft () 

 

5 - Atributo InlineArray en C# 12

Vamos a parar por los Inline arrays, esta funcionalidad bueno, no tenía muy claro ni si incluirla aquí porque no tengo muy claro quién la va a utilizar.

 

Los inline arrays es un atributo de C# que nos permite definir una secuencia de primitivos. Como buen array, tienes que saber el tamaño, pero no a la hora de instanciar el array, si no a la hora de compilar, ya que es en tiempo de compilación donde el tamaño del array va a ser definido:

[InlineArray(10)]
public struct TestArray
{
    private int _element;
}

//Uso
TestArray array = new TestArray();
//Se itera igual que un array normal
for (int i = 0; i < 10; i++)
{
    array[i] = i;
}

foreach (var i in array)
{
    Console.WriteLine(i);
}

El hecho de que tengan que su contenido tenga que ser tipos primitivos, nos permite crear el InlineArray como struct y debido a ello estará localizado en el stack, no en el heap , lo que lo hace algo más eficiente.

Ahora que está explicado, como he dicho antes, quien la va a utilizar? más allá de desarrolladores de videojuegos, quizá? 

 

 

6 - Interceptores en C# 12

En C# 12 introducen interceptores, lo cual siente un poco como un deja vu, ya que bueno los interceptores existen, ya los vimos en este blog en Entity Framework, y si no creo recordar mal existen tambien en .NET Framework, aunque la verdad yo nunca los he utilizado.

Pero bueno, estos de C# 12 son diferentes, y solo tienen igual el nombre. Para variar, otro nombre que, en mi opinión, Microsoft no ha acertado.

 

Pero vayamos a lo que nos atrae, un interceptor no es más que un método que reemplaza un método para llamarse a sí mismo. Lo que quiere decir que el código que escribimos va  a reemplazar al existente. 

Y esta sustitución se hace durante la compilación, no durante la ejecución, y menos mal, porque si hubiera sido en runtime, pues bueno, hacks hacks everywhere. 

 

Veamos un ejemplo sencillo, primero debemos activar la funcionalidad en nuestro proyecto

<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Interceptors</InterceptorsPreviewNamespaces>

Esto quiere decir que están en modo preview, osea que no están lanzados de forma oficial y pueden cambiar. Ahora necesitamos una clase, que es un atributo la cual vamos a crear nuestro InterceptsLocationAttribute

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    sealed class InterceptsLocationAttribute : Attribute
    {
        public InterceptsLocationAttribute(string filePath, int line, int character)
        {

        }
    }
}
  • Nota: como puedes ver hemos especificado un namespace concreto, eso es porque ese atributo va a existir, simplemente aún no lo hace y lo que hacemos es incluirlo nosotros en nuestro propio proyecto.

 

Ahora, debemos de hacer la propia intercepción, osea especificar el código que se va a reemplazar, para ello debemos poner el atributo, el cual incluye el fichero (la ruta completa), la línea y la columna de la primera letra del método que vamos a re-emplazar:

public static class Interceptores
{ 
    [InterceptsLocation(@"C:\Repos\csharp12\Interceptors\Program.cs", line: 3, character: 25)]
    public static int Interceptor1(this Operacion obj, int a, int b)
    {
        Console.WriteLine("esto es el primer interceptor");
        return a * b;
    }
 
    [InterceptsLocation(@"C:\Repos\csharp12\Interceptors\Program.cs", line: 4, character: 25)]
    public static int Interceptor2(this Operacion obj, int a, int b)
    {
        Console.WriteLine("esto es el segundo interceptor");
        return a - b;
    }
}

Lo que podemos notar es que es una clase estática y el método estático es el que va a reemplazar el código que utilizamos, así mismo va a recibir un parámetro “this” con el tipo que estamos reemplazando:

 

Para este ejemplo vamos a reemplazar el uso de este método: 

public class Operacion
{
    public int Sumar(int a, int b)
    {
        return a + b;
    }
}

Y finalmente este es el uso:

Console.WriteLine("Hello, World!");
Operacion operacion = new Operacion();
var result1 = operacion.Sumar(3, 5);
var result2 = operacion.Sumar(10, 5);

Console.WriteLine($"resultado1 {result1}");
Console.WriteLine($"resultado2 {result2}");

Una vez compilamos podemos ver en el IL code que tenemos la llamada a los métodos que hemos creado:

interceptores c# 12

Hay varios temas aquí que me chirrían:

 

El primero es el tema de la ruta, por ahora tiene que ser absoluta, eso quiere decir que necesitas un sitio fijo para el proyecto, tu y bueno si alguien clona tu código, debe estar en la misma ruta, lo cual no tiene mucho sentido. 

Y luego los típicos cómo incluir código malicioso, sobreescribir funcionalidades o similares, pero vaya, nada serio ya que funciona en tiempo de compilación. 

Por cierto los interceptores solo pueden sobre-escribir código de tu proyecto, no de paquetes nuget. Lo cual en mi opinión sería lo ideal. Y siendo sincero no veo la utilidad de esta funcionalidad. 

 

 

7 - Inyección de dependencias con keys

Desde esta nueva versión de C# vamos a tener acceso a lo que se llama keyed services dentro de la inyección de dependencias.

 

Y te preguntarás, qué es un keyed service? Pues muy sencillo, cuando en la inyección de dependencias inyectamos una interfaz múltiples veces, lo que sucede por detrás es que tenemos una lista. Por ejemplo dado el siguiente código, nos inserta  una lista de IEmailProvider:

builder.Services.AddScoped<IEmailProvider, GmailProvider>();
builder.Services.AddScoped<IEmailProvider, HotmailProvider>();

Y luego inyectamos nos devuelve el último que hemos inyectado, pero si lo cogemos manualmente, vemos que podemos elegir tanto el primero con GetService, o la lista. 

Pero ahora tenemos la habilidad de indicar AddKeyedService esto quiere decir que podemos indicar una key, osea un identificador a nuestro servicio:

builder.Services.AddKeyedScoped<IEmailProvider, GmailProvider>("gmail");
builder.Services.AddKeyedScoped<IEmailProvider, HotmailProvider>("hotmail");

Y luego a la hora de cogerlo, indicamos el identificador que queremos leer:

IEmailProvider emailProvider = app.Services.GetKeyedService<IEmailProvider>("gmail");

 

Por supuesto si estamos en un servicio, podemos inyectar el que queremos leer:

public class SendEmail
{
    private readonly IEmailProvider _emailProvider;

    public SendEmail([FromKeyedServices("gmail")]IEmailProvider emailProvider)
    {
        _emailProvider = emailProvider;
    }
}

 

Y esto es muy muy útil, porque anteriormente lo que tenías que hacer es inyectar la lista e ir buscando el que quieras tu, como puedes ver en este trozo de código de mi librería distribt.

Pero ahora te puedes preguntar, ok, ¿pero esto para qué sirve en el mundo real? Un ejemplo muy básico es el siguiente, imagínate que tienes el siguiente código: 

public class SendEmail
{
    private readonly IEmailProvider _emailProvider;

    public SendEmail(IEmailProvider emailProvider)
    {
        _emailProvider = emailProvider;
    }

    public async Task<bool> Execute(string subject, string body, string to)
    {
        //validations, etc
        await _emailProvider.SendEmail(subject, body, to);

        return true;
    }
}

Una clase “SendEmail” la cual envía emails utilizando tu proveedor de confianza, gmail, pero, por el motivo X quieres migrar a hotmail. 

 

En el mundo anterior a keyed services hay varias formas de atajar esto, una es creando una interfaz IHotmail que heredé de IEmailProvider y con eso pues inyectas esa interfaz, otra es crear una nueva interfaz IHotmailProvider que no herede de la anterior e inyectar esa, etc. Pues con  keyed services podemos inyectar ambos servicios con la interfaz correcta, simplemente indicando cual, así que durante la migración lo tenemos mucho más fácil y sin “ensuciar” el resto del código.

public class SendEmail
{
    private readonly IEmailProvider _originalEmailProvider;
    private readonly IEmailProvider _newEmailProvider;

    public SendEmail([FromKeyedServices("gmail")]IEmailProvider originalEmailProvider, 
        [FromKeyedServices("hotmail")]IEmailProvider newEmailProvider)
    {
        _originalEmailProvider = originalEmailProvider;
        _newEmailProvider = newEmailProvider;
    }

    public async Task<bool> Execute(string subject, string body, string to)
    {

        try
        {
            //validations, etc
            await _newEmailProvider.SendEmail(subject, body, to);

            return true;
        }
        catch (Exception e)
        {
            //validations, etc
            Console.WriteLine($"el nuevo provider no funciona {e.Message}");
            await _originalEmailProvider.SendEmail(subject, body, to);

            return true;
        }
    }
}



Y ya esta, esto son todas las novedades de la nueva versión de C# 12!!

Si te ha gustaod el post, no olvides en compartir!


Un saludo


Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2024 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café