Como cada año desde que empecé este blog, del que por cierto es la 5a edición vamos a ver las novedades que nos trae el lenguaje de C# en su nueva versión C# 13.
Antes de empezar cabe destacar que C#13 o bueno .NET 9 cuando salga (del que por supuesto haremos un análisis) es una versión de soporte corto, lo que quiere decir que en 18 meses dejará de tener soporte, así que me imagino que muchos de vosotros no la usareis en las empresas.
En este post no vamos a entrar en los detalles de las mejoras de rendimiento de .NET 9, para eso Microsoft tiene un blog muy completo donde es posible que tu navegador pete, porque es muy muy extenso. Aquí tienes el link: Enlace al blog de las mejoras de rendimiento en net 9.
Si quieres probar las mejoras de C# 13, necesitas instalar .NET 9 en tu máquina, ya que van de la mano.
Índice
- 1 - Nueva política de breaking changes en C#
- 2 - Sincronización de hilos con Lock
- 3 - Cambios en la keyword params
- 4 - Operador de índice implícito al inicializar objetos
- 5 - Mejora en la resolución de tipos naturales para grupos de métodos
- 6 - Nueva secuencia de escape
- 7 - miembros parciales
- 8 - ref y usnafe en iteradores y métodos async
- 9 - Cambios que no están listos para C# 13
TL;DR: syntax sugar y mejoras de rendimiento.
1 - Nueva política de breaking changes en C#
Sin ser una mejora como tal al lenguaje pero sí a la forma en la que se crea el lenguaje,para mi, es lo mejor que nos trae esta versión.
Todos sabemos lo que ha ido sucediendo en estos últimos años, cuando una funcionalidad re-emplazaba a otra lo que hemos tenido es dos formas diferentes de hacer lo mismo en C#, lo cual puede parecer una buena idea, pero no lo es, sobre todo para los junior, ya que les puede confundir.
Esta política ha sido así para no tener breaking changes y parece que ha cambiado, desde el equipo de Microsoft han decidido que, en ciertas ocasiones vamos a tener breaking changes, y es lo que hay. En mi opinión es una buena decisión y la comparto, aunque personalmente marcaría lo que sea que se vaya a quitar como obsoleto en una LTS y luego quitarlo en la siguiente LTS o dos versiones después.
2 - Sincronización de hilos con Lock
La palabra clave lock existe en C# desde C# 1, y en más de 20 años no ha cambiado su funcionamiento. En C# 13 se ha introducido una nueva versión que está en System.Threading.Lock
, la cual no reemplaza a la actual, sino que mejora lo que hay.
Ambos funcionan de una forma similar, o bueno, buscan el mismo objetivo, evitar que múltiples hilos modifiquen el mismo objeto de forma simultánea.
La gran ventaja que tiene es que ahora permite implementar el patrón Dispose a través de su método EnterScope() y permite el uso de la keyword using, lo que implica que si salta una excepción el recurso se liberará de forma segura mejorando la fiabilidad.
Esta es la nueva sintaxis:
System.Threading.Lock myLock = new System.Threading.Lock();
lock (myLock)
{
}
//Internamente
using (myLock.EnterScope())
{
// Código
}
En resumen, de cara a la sintaxis del lenguaje que vamos a utilizar, va a ser la misma. Su implementación es mucho más potente porque mejora el rendimiento y evitará algunos de los riesgos que teníamos anteriormente dentro de un lock.
Aquí un enlace a la explicació para el patrón Dispose.
3 - Cambios en la keyword params
La keyword params
es una de esas palabras clave que nunca sabes porque estan ahí hasta que las necesitas.
Se puede resumir en que la keyword param te permite que un método acepte varios parámetros de entrada de un tipo específico, y que por detrás los convierte a un array.
Quizá esté equivocado pero creo que en las primeras versiones de C# la estructura de una aplicación de consola era tal que así:
class Program
{
static void Main(params string[] args)
{
Console.WriteLine("Hello, World!");
}
}
Esto implica que puedes pasar tantas variables string como quieras y serán convertidas en un array, ese ejemplo de código es con el método de entrada, pero cualquier método puede usar la keyword params, y luego su uso es tan simple como lo siguiente:
Main("a", "b" ,"c")
El cambio que tenemos en C#13 es para que la palabra clave params no funcione únicamente con arrays, sino que también lo haga con otro tipo de colecciones como son List, IEnumerable, o Span lo que aumenta la flexibilidad del código ya que no limitamos únicamente a los arrays sino que podemos utilizar colecciones más modernas y eficientes.
4 - Operador de índice implícito al inicializar objetos
Junto a la llegada de pattern matching hace un par de versiones, llegó el operador `^
`, el cual permite acceder a los elementos de un array empezando desde el final.
Si tienes una array int[]
y haces int[^1]
lo que haces es acceder al último elemento del array, si haces int[^2]
lo que haces es acceder al penúltimo elemento.
Con este nuevo cambio podemos no solo acceder a los elementos del array, sino que podemos también asignar valores a la hora de inicializar un objeto.
Para verlo con un ejemplo, en circunstancias normales asignas valores en orden:
int[] example = new int[]
{
1, 2, 3
};
Lo que te da una salida [1,2,3]
Si lo indicas con la nueva funcionalidad
ExampleInverseAssignation exampleInverseAssignation = new ExampleInverseAssignation
{
Values =
{
[^1] = 1,
[^2] = 2,
[^3] = 3
}
};
class ExampleInverseAssignation
{
public int[] Values { get; set; } = new int[3];
}
Estarías empezando a asignar valores por el último elemento, por lo que el array sería [3,2,1]
.
La pregunta de si tiene utilidad o no, es que bueno, alguna hay porque a todos nos ha pasado tener que iterar una array en sentido contrario alguna vez, pero es, por lo menos en mi experiencia, una situación que apenas se ve y nos permite ahorrar cálculos lo cual siempre va a mejorar el rendimiento principalmente en algoritmos de inversión de listas, procesamiento de datos o incluso en la legibilidad del código.
5 - Mejora en la resolución de tipos naturales para grupos de métodos
No voy a entrar puramente en detalle pues esta sección se hace completamente en el compilador, en resumen, la forma en la que el código elige qué método tiene que ejecutar es más eficiente y por lo tanto más rápida.
Para ponerte en contexto, hay situaciones en las que el compilador tiene una lista de posibles métodos que pueden ser ejecutados y lo que hace es evaluar cada uno para ver cual es el correcto, lo cual es ineficiente cuando se usan generics con restricciones complejas porque hay que analizar toda la lista completa.
En C# 13 se ha introducido un proceso más eficiente que primero va a reducir el número de métodos a evaluar primero por “ámbito” osea si un método está en la propia instancia o es un método extensible, y luego eliminando de la lista métodos que no van a poder ser ejecutados, como generics con diferente número de parámetros o que no cumplen las restricciones.
Al eliminar todos estos métodos se reduce la carga de trabajo del compilador y por lo tanto más eficiencia.
6 - Nueva secuencia de escape
Cambio menor, pero que quizá a alguien le afecte (a mi no) se ha introducido una nueva secuencia de escape `\e
` que representa el carácter ESCAPE
.
Donde anteriormente debíamos usar el caracter unicode \u001b
o \x1b
, lo que podía traer problemas ya que si después de el `1b
` en cualquiera de las dos opciones tenías caracteres hexadecimales válidos se interpretaba como parte de la secuencia de escape lo que obviamente es un problema ya que causaba resultados inesperados.
7 - miembros parciales
Entiendo que todos sabréis lo que son las clases parciales, y siguiendo la misma lógica que dichas clases, ahora podemos tener propiedades e indexers parciales.
Lo que podemos hacer ahora es declarar miembros parciales y esas declaraciones necesitan ser implementadas, lo cual es útil cuando quieres que otros puedan extender dicha funcionalidad.
Por ejemplo, puedes crear un evento parcial y permitir que otras personas agreguen código a la ejecución de dicho evento.
Personalmente le veo un buen uso en source generators.
8 - ref y usnafe en iteradores y métodos async
Antes de C# 13 los métodos que usan yield return y async no podían declarar variables locales ref o utilizar unsafe. En C# 13 esto ya es posible, aunque con ciertas limitaciones.
El efecto que tiene este cambio es que ahora podemos utilizar tipos ref struct como Span<T>
o ReadOnlySpan<T>
que son tipos muy eficientes para trabajar en memoria.
Sobre unsafe
, si no sabéis sobre esta keyword se utiliza para acceder a elementos de la memoria de manera manual o el uso de punteros.
public unsafe IEnumerable<int> GetData()
{
int* data = stackalloc int[10];
for (int i = 0; i < 10; i++)
{
data[i] = i * i;
yield return data[i];
}
}
Este cambio te permite realizar operaciones más avanzadas, como podemos ver en el ejemplo con los punteros, sin comprometer la seguridad en el código que genera los valores para el iterador.
9 - Cambios que no están listos para C# 13
Cuando primero anunciaron los cambios en la nueva versión de C# había una idea de incluir algunos cambios más, los cuales por unos motivos o por otros no han podido llegar a esta versión pero casi seguro lleguen a la siguiente.
9.1 - Propiedades automáticas con lógica personalizada
Si lleváis tiempo en esto sabréis que anteriormente teníamos que definir los getters y los setters de forma manual y luego simplemente cambiamos la sintaxis por get set
.
public class Vehicle
{
public string Make { get; set; }
}
Este cambio fue muy muy bueno porque elimina gran cantidad de código que no se necesitaba. O bueno eso pasaba en la gran mayoría de empresas, porque yo he visto código escrito en 2020 que seguían haciendo los getters y setters antiguos.
En cualquier caso, la forma en la que funciona es que el compilador por detrás te crea el field convirtiéndolo en el siguiente código:
class Vehicle
{
private string _make; // field (compilador)
public string Make // Propiedad (usuario)
{
get => _make;
set => _make = value;
}
}
Todo esto es transparente al usuario, excepto si necesitas añadir alguna configuración adicional, que puede pasar.
Entonces, cuando necesitamos poner alguna configuración ya sea en el getter
o en el setter
, necesitamos escribir de forma manual el backfield, para añadir dicha configuración. Imagina que solo quieres tener coches que son de la marca Audi, para el resto quieres ocultar el valor, una de las formas que lo puedes hacer es la siguiente:
class Vehicle
{
private string _make; // field (compilador)
public string Make // Propiedad (usuario)
{
get => _make;
set
{
if (value == "audi")
{
_make = value;
}
else
{
_make = "";
}
}
}
}
Nota: puedes usar el operador ternario para reducir la sentencia if.
Con la nueva versión de C#13 no necesitamos crear el field de forma manual, sino que el compilador sabe que está ahí en tiempo de desarrollo y podemos acceder a él, lo que nos permite reducir el número de líneas de código.
class Vehicle
{
public string Make // Propiedad (usuario)
{
get => field;
set => field = value == "audi" ? value : "";
}
}
Algo a tener en cuenta que si tu tenías una variable llamada field, el código tendrá un breaking change.
Y al final lo que parece que va a pasar es que van a convertir la palabra clave field en una keyword del propio lenguaje enlacel al proposal en GitHub.
Te puedes suscribir a esta issue de github para seguir al día lo que está pasando.
9.2 - Métodos extensibles para propiedades, indexadores y miembros estáticos
Desde C# 3 tenemos métodos extensibles (extension methods) y personalmente los utilizo muchísimo porque me encantan. Principalmente iban a traer una evolución para ser compatibles con propiedades o indexers.
Como al final han pospuesto todo a C# 14, lo veremos el año que viene, pero si tienes curiosidad, aquí tienes el enlace al anuncio por parte de Microsoft.
9.3 - Union types / Discriminated unions en C#
Aunque no está de forma oficial anunciado para C# 14, hay un proposal para utilizar union types de los que posiblemente hablaremos larga y tendidamente durante 2025.
ACTUALIZACIÓN: Parece ser que son mas complejos de lo que parece, y quizá lo postpongan para C# 16 o C# 17, enlace a clip de youtube.
Pero ese mismo proposal sugiere incluir el tipo Result<T>
como tipo dentro del propio lenguaje, ya sabéis que yo soy gran fan de hacer las aplicaciones con ese estilo de programación y tengo un post y una librería al respecto.
Aquí tienes el enlace a mi blog.