En está ocasión veremos las nuevas características del lenguaje de programación C#:
- Miembros de extensión (extension members).
- Asignación nula condicional.
- Palabra clave
field. -
nameof para genéricos no enlazados. - Constructores y eventos parciales.
- Conversiones implícitas para
Span<T>. - Modificadores en Parámetros Lambda.
1. Miembros de extensión (extension members)
Con los extension members podemos extender interfaces con miembros adicionales, incluyendo propiedades y métodos, sin modificar la interfaz original. Esto abre la puerta a patrones más limpios, especialmente en librerías y arquitectura modular.
Los extension members permiten:
- Añadir implementaciones por defecto a interfaces sin romper compatibilidad.
- Crear APIs más expresivas sin modificar código fuente de terceros.
- Encapsular comportamientos comunes en un solo lugar.
- Reducir boilerplate en implementaciones repetitivas.
- Evolucionar interfaces sin romper implementaciones existentes.
Ejemplo 1. Extender una interfaz con un método.
Teniendo está interface (que no podemos modificar) y agregar un nuevo método para el cálculo del perímetro.
public interface IShape
{
double Area();
}
Podemos agregarlo de esta manera sin tocar la interface:
public interface IShape
{
double Area();
}
public static class ShapeExtensions
{
public static double Perimeter(this IShape shape)
{
return Math.Sqrt(shape.Area() / Math.PI) * 2;
}
}
Ejemplo 2. Extender una interfaz con una propiedad, lo que antes era imposible.
public interface IShape
{
double Area();
}
public static class ShapeExtensions
{
public static double Diameter(this IShape shape)
{
return Math.Sqrt(shape.Area() / Math.PI) * 2;
}
public static double Radius(this IShape shape)
=> Diameter(shape) / 2;
}
Y ahora:
public static class ShapeExtensions
{
public static double Radius(this IShape shape)
=> Math.Sqrt(shape.Area() / Math.PI);
}
Podemos usarlo de esta forma:
IShape circle = new Circle(10);
Console.WriteLine(circle.Radius());
2. Asignación nula condicional
Básicamente es una forma de decir: solo asigna este valor si la variable no es null. Su sintaxis es:
Ejemplo de uso:
Usuario? usuario = ObtenerUsuario();
usuario?.Edad = 30; // Solo si usuario no es null
En lugar de hacer esto:
if(usuario != null){
usuario.Edad = 30;
}
Esto nos ayuda con:
- Menos if repetitivos.
- Código más expresivo y seguro.
- Menos ruido en handlers, mappers y servicios.
- Mejor legibilidad en pipelines donde muchos objetos pueden ser null.
3. Palabra clave field
field es una palabra clave contextual que te permite acceder al campo de respaldo (backing field) que el compilador genera automáticamente para una propiedad. Es decir, te deja usar propiedades automáticas pero con lógica personalizada, sin tener que declarar un campo privado manualmente.
Sirve para:
- Agregar validaciones en el setter sin crear un campo privado.
- Agregar normalización (trim, toUpper, etc.).
- Agregar restricciones (rangos, valores mínimos).
- Mantener el código limpio y compacto.
- Evitar el boilerplate clásico:
private int _edad;
public int Edad
{
get => _edad;
set => _edad = value < 0 ? 0 : value;
}
Con field , ya no necesitas ese campo _edad :
public int Edad
{
get;
set => field = value < 0 ? 0 : value;
}
field nos ayuda a:
- No depender de campos extra o campos privados.
- Podemos hacer uso de propiedades compactas.
- Tener menos código repetitivo.
- Tener un código más legible y expresivo.
4. nameof para genéricos no enlazados
Según la documentación oficial:
nameof ahora soporta tipos genéricos no enlazados (unbound generic types).
Ejemplo 1. Especificar un tipo concreto.
Antes de C# 14:
nameof(List<int>) // "List"
Pero no podías hacer esto:
nameof(List<>) // Error antes de C# 14
Con C# 14 si se pude:
Console.WriteLine(nameof(Dictionary<,>));
// Resultado: "Dictionary"
Console.WriteLine(nameof(List<>));
// Resultado: "List"
5. Constructores y eventos parciales
Con esta nueva carcaterística: puedes declarar constructores como partial, igual que métodos o propiedades.
Esto es útil porque:
- Puedes dividir un constructor entre varios archivos de una clase parcial.
- Una parte puede declarar la firma.
- Otra parte puede contener la implementación.
Ejemplo. Veamos un uso de constructor parcial.
Generado automáticamente:
public partial class Persona
{
public partial Persona(string nombre);
}
Tu implementación:
public partial class Persona
{
public partial Persona(string nombre)
{
Nombre = nombre;
}
public string Nombre { get; }
}
Usar esta nueva característica nos sirve para:
- Separar lógica generada de lógica manual.
- Evitar sobrescribir código cuando regeneras archivos.
- Mantener clases grandes organizadas.
- Permitir que generadores agreguen inicialización sin romper tu constructor.
¿Y qué con los eventos parciales? Pues, ahora puedes declarar eventos como partial.
Esto permite dividir:
- La declaración del evento.
- La implementación del add/remove.
Igual que con los constructores, esto es ideal para escenarios donde parte del código es generado.
¿Para qué sirve?
- Separar la lógica de suscripción generada de la lógica manual.
- Permitir que generadores agreguen eventos sin interferir con tu código.
- Mantener clases grandes más limpias.
6. Conversiones implícitas para Span<T>
Con C# 14 se agregan más conversiones implícitas entre Span<T>,
ReadOnlySpan<T> y (T[]).
¿Para qué sirven?
Estas conversiones implícitas permiten:
- Usar
Span<T> y ReadOnlySpan<T> de forma más natural. - Evitar copias innecesarias de memoria.
- Escribir código más rápido y eficiente sin complicaciones.
- Simplificar APIs que trabajan con buffers o slices de memoria.
- Reducir la fricción al pasar arreglos a métodos que esperan spans.
En pocas palabras:
Hacen que Span<T> sea un ciudadano de primera clase en el lenguaje.
Ejemplo. Si tenías un método que recibía un Span<int> :
void Procesar(Span<int> datos)
{
// ...
}
Y querías pasarle un arreglo, necesitabas conversión explícita:
int[] numeros = { 1, 2, 3, 4 };
// Antes: necesitabas conversión explícita
Procesar(numeros.AsSpan());
Ahora con C# 14 lo haces directamente:
int[] numeros = { 1, 2, 3, 4 };
// Conversión implícita
Procesar(numeros);
Resumen:
| Conversión |
Antes de C# 14 |
Con C# 14 |
Ejemplo |
T[] → Span<T> |
No implícita |
Implícita |
Procesar(numeros); |
T[] → ReadOnlySpan<T> |
No implícita |
Implícita |
Leer(numeros); |
Span<T> → ReadOnlySpan<T> |
No implícita |
Implícita |
ReadOnlySpan<byte> r = buffer; |
string → ReadOnlySpan<char> |
Ya existía |
Mejorada |
Imprimir("Hola"); |
7. Modificadores en Parámetros Lambda
Esta nueva característica nos permite usar modificadores en los parámetros de una expresión lambda, igual que en un método normal.
Los modificadores permitidos incluyen:
Esto significa que puedes escribir lambdas que:
- Pasan argumentos por referencia.
- Evitan copias innecesarias.
- Trabajan con parámetros de solo lectura.
- Aceptan un número variable de argumentos.
Con esto, las lambdas ahora pueden comportarse como métodos completos.
¿Para qué sirven?
- Para mejorar el rendimiento (evitar copias grandes).
- Para permitir APIs más flexibles.
- Para trabajar con estructuras (struct) sin overhead.
- Para escribir código más expresivo y seguro.
- Para usar lambdas en escenarios donde antes solo podías usar métodos normales.
Ejemplo 1. Uso de lambda con in (solo lectura). Supongamos que tienes una estructura grande:
public struct Punto
{
public int X;
public int Y;
}
Antes, si querías procesarla en una lambda, se copiaba completa. Ahora puedes usar in:
Action<in Punto> imprimir = (in Punto p) =>
{
Console.WriteLine($"({p.X}, {p.Y})");
};
Uso:
var punto = new Punto { X = 10, Y = 20 };
imprimir(in punto);
Con esto:
- No se copia la estructura.
- Solo de lectura.
- Es más eficiente.
Ejemplo 2. Uso de lambda con ref.
var numero = 5;
Action<ref int> duplicar = (ref int n) =>
{
n *= 2;
};
duplicar(ref numero);
Console.WriteLine(numero); // 10
Con esto:
La lambda modifica la variable original. Igual que un método con ref.
Resumen:
| Modificador |
¿Qué hace? |
¿Para qué sirve? |
Ejemplo |
in |
Pasa el parámetro por referencia de solo lectura |
Evita copias de estructuras grandes |
(in Punto p) => Console.WriteLine(p.X); |
ref |
Pasa el parámetro por referencia modificable |
Permite cambiar el valor original |
(ref int n) => n *= 2; |
out |
Parámetro de salida |
Devolver múltiples valores desde una lambda |
(out int r) => r = 10; |
params |
Acepta un número variable de argumentos |
Más flexibilidad en lambdas |
(params int[] v) => v.Sum(); |
C# es un lenguaje de programación muy completo que puede ser usado en muchos ámbitos. Se puede emplear casi cualquier paradigma de programación existente como la POO y la Programación funcional. Sus nuevas características lo extienden aún más y proveen al desarrollador más herramientas para hacer de su código algo más eficiente y de calidad.
Resumiendo todo lo anteriormente visto tenemos una tabla resumen de lo anteriormente visto:
| Característica |
¿Qué es? |
¿Para qué sirve? |
Ejemplo breve |
Miembros de extensión (extension members) |
Permiten extender interfaces con métodos, propiedades y más. |
Agregar funcionalidad sin modificar el tipo original. |
IShape s; s.Perimeter(); |
| Asignación nula condicional |
Permite asignar solo si el objeto no es null. |
Evita if (x != null) repetitivos. |
persona?.Nombre = "Juan"; |
Palabra clave field |
Accede al campo de respaldo de una propiedad automática. |
Permite validaciones sin declarar campos privados. |
set => field = value.Trim(); |
nameof para genéricos no enlazados |
Permite usar nameof con tipos genéricos abiertos. |
Mejora logging, diagnósticos y metaprogramación. |
nameof(Dictionary<,>) |
| Constructores y eventos parciales |
Permiten dividir su declaración e implementación. |
Separar código generado del código manual. |
public partial Persona(...) |
Conversiones implícitas para Span<T> |
Permiten convertir arreglos y spans sin código explícito. |
Mejor rendimiento y APIs más limpias. |
Procesar(numeros); |
| Modificadores en parámetros lambda |
Permiten usar ref, in, out, params en lambdas. |
Más control, eficiencia y expresividad. |
(ref int n) => n++; |
Seguiremos con esta serie sobre C#.
Enlaces:
https://learn.microsoft.com/es-es/dotnet/csharp/whats-new/csharp-14