Hace unos días se lanzó .NET 10 y, con él, C# 14, una nueva versión del lenguaje que viene con varias novedades interesantes que mejoran la productividad y la experiencia de desarrollo.
Las más destacables son:
- Miembros extensores
- Asignaciones condicionales
- Propiedades semi-automáticas
- Simplificación del uso de
nameofcon tipos genéricos - Eventos y constructores parciales
- Conversiones implícitas para
Span<T>yReadOnlySpan<T> - Simplificación de parámetros de lambdas
Les damos un vistazo rápido a continuación.
Miembros extensores
Hasta C# 13, los métodos extensores (extension methods) eran la única forma de añadir funcionalidad o características a tipos existentes sin necesidad de modificar su código fuente original. Sin embargo, esta técnica tenía limitaciones, como la imposibilidad de añadir propiedades o eventos, o definirlas sobre clases estáticas. Además, la forma en la que los implementábamos no favorecía especialmente la legibilidad del código.
En C# 14, los métodos extensores siguen siendo válidos y retrocompatibles a nivel de código, pero el concepto ha sido ampliado significativamente. A partir de esta versión, se han introducido los bloques extension como delimitadores para definir miembros extensores de un mismo tipo de una forma más estructurada y legible. Observa el siguiente código, donde podemos ver la comparativa entre la sintaxis clásica y la nueva:
// C# 13 y anteriores
public static class StringExtensions
{
public static bool IsCapitalized(this string str)
{
return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
}
public static bool IsInteger(this string str)
{
return int.TryParse(str, out _);
}
}
// C# 14:
public static class StringExtensions
{
extension(string str)
{
public bool IsCapitalized()
{
return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
}
public bool IsInteger()
{
return int.TryParse(str, out _);
}
}
}
Pero la cosa no queda ahí 🙂. Además del bloque extension, C# 14 introduce la posibilidad de definir otros tipos de miembros extensores, tanto de instancia como estáticos:
public static class StringExtensions
{
extension(string str)
{
// Propiedad extensora:
public char? FirstCharacter
{
get => string.IsNullOrEmpty(str) ? null : str[0];
}
}
extension(Math)
{
// Extensión de clase estática:
public static int Factorial(int n)
{
if (n < 0) throw new ArgumentException("Invalid negative value", nameof(n));
return n == 0 ? 1 : n * Factorial(n - 1);
}
}
}
Podéis leer más sobre miembros extensores en este post de hace unas semanas.
Asignaciones condicionales
Llevamos tiempo usando operadores como null coalescing (??), null coalescing assignment (??=) o safe navigation (?.) para lidiar con valores nulos de una forma más concisa y legible. En C# 14 se ha extendido este último para permitir su aplicación en el lado izquierdo de una asignación, permitiéndonos asignar valores a propiedades o campos de objetos que podrían ser nulos, sin necesidad de realizar comprobaciones explícitas.
// C# 13 y anteriores:
var friend = GetFriendOrNull(1);
if (friend != null)
{
friend.Name = "John Doe";
}
// C# 14:
var friend = GetFriendOrNull(1);
friend?.Name = "John Doe"; // Si friend es nulo, no se lanza excepción y no se asigna valor
Si queréis profundizar algo más, hace algún tiempo hablamos de esta característica en este otro post.
Propiedades semi-automáticas
Las propiedades semiautomáticas son una mezcla entre las propiedades automáticas y las tradicionales con backing field, pero tomando lo mejor de ambos mundos. Gracias a esta característica podemos definir propiedades con un campo de respaldo generado automáticamente, pero al mismo tiempo nos dan la flexibilidad de acceder a él mediante la palabra clave contextual field, lo que nos permite personalizar los accesores get y set según nuestras necesidades.
// C# 13 y anteriores:
private int _age;
public int Age
{
get { return _age; }
set {
if (value < 0) throw new ArgumentException("Age cannot be negative");
_age = value;
}
}
// C# 14:
public int Age
{
get; // Retorna el valor del backing field generado automáticamente
set
{
if (value < 0) throw new ArgumentException("Age cannot be negative");
field = value; // 'field' es el backing field
}
}
Para ver esta característica en mayor detalle, podéis consultar el post Propiedades semi-automáticas en C# que publiqué por aquí hace ya casi un año.
Uso de nameof con tipos genéricos
El operador nameof ha sido mejorado para permitir el uso de tipos genéricos sin especificar sus parámetros de tipo, algo que antes no era posible.
Console.WriteLine(nameof(List<>)); // Imprime "List"
Constructores y eventos parciales
También es interesante comentar que ahora se permite la implementación de constructores y eventos parciales. En la práctica, esto quiere decir que podemos definir este tipo de miembros en una parte de una clase parcial, y completarlos en otra.
Como podemos ver en el siguiente ejemplo, en la primera porción de la clase definimos los elementos como partial y en la segunda los implementamos. Fijaos que en el caso del evento, debemos definir los bloques add y remove para gestionar los suscriptores:
// Definición en una parte de la clase parcial:
public partial class MyClass
{
public partial MyClass(string something); // Partial constructor declaration
public partial event EventHandler? OnMyEvent; // Partial event declaration
}
// Implementación en otra parte de la clase parcial:
public partial class MyClass
{
public partial MyClass(string something)
{
// Constructor implementation
};
private EventHandler? _onMyEvent;
public partial event EventHandler? OnMyEvent
{
add
{
_onMyEvent += value; // Subscriber added
}
remove
{
_onMyEvent -= value; // Subscriber removed
}
}
}
Conversiones implícitas para Span<T> y ReadOnlySpan<T>
C# 14 introduce conversiones implícitas para los tipos Span<T> y ReadOnlySpan<T>, facilitando su uso y manipulación sin necesidad de conversiones explícitas.
Por ejemplo, el bloque de código mostrado a continuación no compila en versiones anteriores a C# 14, porque el método StartsWith() es un extensor del tipo ReadOnlySpan<int> y no existe en la clase Array. Sin embargo, en C# 14, gracias a las conversiones implícitas, este código funciona correctamente:
int[] arr = [1, 2, 3];
Console.WriteLine(arr.StartsWith(1)); // Muestra "True" en C# 14
Modificadores simplificados en parámetros lambda
En C# 14, se pueden usar modificadores de parámetros como scoped, ref, in, out, o ref readonly en expresiones lambda sin necesidad de especificar el tipo del parámetro, haciendo el código algo más conciso y legible.
Por ejemplo, observa la sutil diferencia entre C# 13 y 14 en la siguiente definición de un delegado que intenta parsear un valor:
// Definición del delegado
delegate bool TryParse<T>(string text, out T result);
// C# 13 y anteriores
TryParse<int> parser = (string text, out int result) => int.TryParse(text, out result);
// C# 14
TryParse<int> parser = (string text, out result) => int.TryParse(text, out result);
La segunda opción no compilará en versiones anteriores de C# porque en el parámetro out de la expresión lambda no se especifica el tipo int. En C# 14 es perfectamente válido, porque puede ser inferido del delegado.
Punto extra: ¿podemos usar C# 14 en proyectos .NET 9 o Visual Studio 2022?
Pues sí, es posible porque estamos hablando de características del lenguaje y no del framework, así que podremos aprovechar estas novedades en proyectos dirigidos a versiones anteriores de .NET y con Visual Studio 2022.
Para ello, simplemente debemos asegurarnos de configurar la versión del lenguaje en la propiedad LangVersion del archivo de proyecto (.csproj), especificando latest o 14.0, como se muestra a continuación:
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
Publicado en Variable not found.


Aún no hay comentarios, ¡sé el primero!
Enviar un nuevo comentario