1. Introducción
Antes solo se podían añadir a las clases extension methods. Pero no se podía añadir una propiedad (property) como extensión de clase. O sea, si querías que cualquier DateTime tuviera la propiedad IsWeekend, tenías que hacerlo con un método, lo cual no siempre es cómodo — sobre todo si quieres la sintaxis date.IsWeekend en vez de date.IsWeekend().
Con la llegada de C# 14 el sueño se hizo realidad: ahora puedes escribir propiedades de extensión de una forma parecida a los métodos. Te permiten añadir una nueva propiedad a un tipo existente sin tocar su código original, ¡y usarlas como si fueran propiedades normales!
¿Dónde es realmente útil?
- Para tipos estándar de .NET y librerías de terceros cuyo código no puedes modificar.
- Para propiedades "virtuales" cómodas en view models, UI, vistas calculadas.
- Donde quieres una sintaxis bonita tipo .SomeCalculatedProperty sin paréntesis extra.
2. Nueva sintaxis en C# 14
En C# 14 decidieron dejar de usar la palabra clave this para extensiones. En vez de eso, introdujeron el operador especial extension(Type this). Y con él puedes añadir nuevas entidades virtuales a una clase.
Sintaxis antigua
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string str)
=> string.IsNullOrWhiteSpace(str);
}
Nueva sintaxis
public static class Enumerable
{
extension(TSource source)
{
// Miembros virtuales para el objeto
}
extension(TSource)
{
// Miembros estáticos virtuales
}
}
3. Sintaxis de propiedades de extensión
¿Cómo se ve?
Supón que quieres añadir al tipo DateTime una propiedad que devuelva si la fecha es un día de fin de semana:
public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
Pero esto es la versión antigua de Extension Method, y se llama así: myDate.IsWeekend()
Nuevo estilo — Extension Property
public static class DateTimeExtensions
{
extension(DateTime source)
{
public bool IsWeekend // ¡propiedad de extensión!
{
get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
}
}
}
Ahora puedes escribir así:
DateTime today = DateTime.Now;
if (today.IsWeekend)
{
Console.WriteLine("¡Puedes dormir más!");
}
4. Tipos de extension properties
Puedes añadir no solo propiedades de solo lectura, sino también de lectura y escritura:
public static class DateTimeExtensions
{
extension(DateTime source)
{
public int YearFrom1900 // ¡propiedad de extensión!
{
get => source.Year - 1900;
set => source.Year = value + 1900;
}
}
}
5. Propiedades estáticas
Gracias a la nueva sintaxis, puedes añadir propiedades virtuales no solo al objeto, ¡sino también a la clase! Sí, lo has entendido bien, ahora podemos añadir propiedades estáticas virtuales a la clase. La sintaxis es casi igual:
public static class DateTimeExtensions
{
// Nueva sintaxis: extension block para DateTime (C# 14)
extension(DateTime)
{
// Propiedad estática para guardar la zona horaria actual del usuario
private static TimeZoneInfo _currentTimeZone = TimeZoneInfo.Local;
// Propiedad de extensión para acceder a la zona horaria actual
public static TimeZoneInfo CurrentTimeZone
{
get => _currentTimeZone;
set => _currentTimeZone = value ?? TimeZoneInfo.Local;
}
// Devuelve la hora en la zona horaria del usuario
public DateTime InCurrentTimeZone
{
get => TimeZoneInfo.ConvertTime(this, CurrentTimeZone);
}
}
}
6. Ejemplos: ¡ampliamos la clase Dog!
Vamos a seguir desarrollando nuestra app de perros, que llevamos arrastrando de lección en lección.
Supón que ahora tenemos no solo perros, sino también una lista de sus vacunas (List<DateTime>), pero la clase Dog viene de una librería externa y no podemos cambiar su código. Queremos añadir una propiedad: ¿Tiene todas las vacunas necesarias este año?
Forma antigua (método de extensión):
public static class DogExtensions
{
public static bool AllVaccinatedThisYear(this Dog dog)
{
return dog.Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
// Uso:
if (myDog.AllVaccinatedThisYear())
Console.WriteLine("¡El perro está sano!");
Nueva forma (propiedad de extensión):
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// Uso:
if (myDog.AllVaccinatedThisYear)
Console.WriteLine("¡El perro está sano!");
Ejemplo completo
// Clase Dog — por ejemplo, de una librería externa:
public class Dog
{
public string Name { get; set; }
public List<DateTime> Vaccinations { get; set; } = new List<DateTime>();
}
// Clase de extensiones con extension block:
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// En el programa:
var myDog = new Dog { Name = "Buddy" };
myDog.Vaccinations.Add(new DateTime(DateTime.Now.Year, 2, 16)); // vacuna este año
Console.WriteLine($"{myDog.Name}: ¿vacunada? {(myDog.AllVaccinatedThisYear ? "Sí" : "No")}");
7. Tabla: Extension Methods vs Extension Properties
| Método de extensión | Propiedad de extensión | |
|---|---|---|
| Sintaxis de llamada | obj.Method() | obj.Property |
| Pasa parámetros | Sí | No (solo this obj) |
| Se puede escribir | No aplicable | Solo si está implementado |
| Cómodo para View/UI | A veces | Sí (binding bidireccional, lambda) |
| Visibilidad en reflexión | No | No |
8. Trampas potenciales y errores típicos
Cuando las extension properties no ayudan
- No pueden sustituir completamente a los campos reales: no tienen acceso a miembros privados del objeto.
- No puedes añadir un cambio automático de estado del objeto (por ejemplo, no puedes controlar el cambio de valor en la clase original directamente).
- No puedes usar extension property para implementar una interfaz o clase abstracta.
Error con los nombres
Si de repente en la clase original aparece una propiedad con el mismo nombre que la tuya, se usará la propiedad "nativa" y no tu extensión. Así que ten cuidado al elegir nombres, sobre todo cuando trabajas con tipos públicos de .NET.
Problemas con serialización/reflexión
Extension property no es un miembro "real" del tipo, sino solo azúcar sintáctico. Por eso, si usas reflexión o serialización, la propiedad de extensión puede no ser visible para esas herramientas.
GO TO FULL VERSION