Los métodos de extensión de C# siempre fueron una de las features que más me gustaron. Pero siempre tuvieron una limitación molesta: solo podías agregar métodos, no propiedades. Si querías que lista.IsEmpty funcionara como propiedad (sin paréntesis), no había forma elegante. C# 14 lo soluciona con los bloques de extensión.
El límite de los métodos de extensión clásicos
// ❌ Métodos de extensión clásicos: solo métodos, sintaxis dispersa
public static class EnumerableExtensions
{
// No hay forma de hacer esto una propiedad — requiere paréntesis
public static bool IsEmpty<T>(this IEnumerable<T> source)
=> !source.Any();
public static IEnumerable<T> WhereNot<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
=> source.Where(x => !predicate(x));
}
// Uso: requiere paréntesis aunque semánticamente sea una propiedad
var lista = new List<int>();
bool vacia = lista.IsEmpty(); // paréntesis obligatorios
Extension members: la nueva sintaxis de bloques
// ✅ C# 14: bloques de extensión con propiedades reales
public extension EnumerableExtensions<T>(IEnumerable<T> source)
{
// Propiedad de extensión: acceso sin paréntesis
public bool IsEmpty => !source.Any();
public int Count => source.Count();
// Método de extensión en el mismo bloque
public IEnumerable<T> WhereNot(Func<T, bool> predicate)
=> source.Where(x => !predicate(x));
public IEnumerable<T> Shuffle()
{
var lista = source.ToList();
var rng = Random.Shared;
for (int i = lista.Count - 1; i > 0; i--)
{
int j = rng.Next(i + 1);
(lista[i], lista[j]) = (lista[j], lista[i]);
}
return lista;
}
}
// Uso: propiedades sin paréntesis
var numeros = new List<int>();
bool vacia = numeros.IsEmpty; // sin paréntesis
int cant = numeros.Count; // sin paréntesis
var pares = Enumerable.Range(1, 10).WhereNot(n => n % 2 == 0);
Miembros estáticos de extensión
Una de las capacidades nuevas más potentes: agregar métodos de fábrica estáticos a tipos que no controlamos.
// Extender DateTime con métodos de fábrica estáticos
public extension DateTimeExtensions(DateTime _)
{
// Miembro estático: se llama como DateTime.HoyArgentina()
public static DateTime HoyArgentina()
{
var tz = TimeZoneInfo.FindSystemTimeZoneById("Argentina Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
}
// Propiedad de instancia: sin paréntesis
public bool EsFinDeSemana => DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
public string FormatoArgentino() => ToString("dd/MM/yyyy HH:mm");
}
// Uso
var hoy = DateTime.HoyArgentina(); // método estático
bool finde = DateTime.Now.EsFinDeSemana; // propiedad de instancia
string fmt = DateTime.Now.FormatoArgentino(); // método de instancia
Extensiones para tipos del framework que usamos todos los días
// Extensiones útiles para string
public extension StringExtensions(string source)
{
public bool IsNullOrEmpty => string.IsNullOrEmpty(source);
public bool IsNullOrWhiteSpace => string.IsNullOrWhiteSpace(source);
public string Truncar(int maxLength)
=> source.Length <= maxLength ? source : source[..maxLength] + "...";
public string ToCamelCase()
{
if (source.IsNullOrEmpty) return source;
return char.ToLowerInvariant(source[0]) + source[1..];
}
}
// Extensiones para HttpClient
public extension HttpClientExtensions(HttpClient client)
{
public async Task<T?> GetJsonAsync<T>(string url)
{
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
}
// Uso
string nombre = "MiServicio";
Console.WriteLine(nombre.ToCamelCase()); // "miServicio"
Console.WriteLine(nombre.Truncar(5)); // "MiSer..."
var usuario = await httpClient.GetJsonAsync<Usuario>("/api/usuarios/1");
Diferencias clave con los métodos de extensión clásicos
| Característica | Métodos de extensión (clásico) | Extension members (C# 14) |
|---|---|---|
| Métodos de instancia | ✅ | ✅ |
| Propiedades de instancia | ❌ | ✅ |
| Miembros estáticos | ❌ | ✅ |
| Agrupación en bloque | ❌ (clase estática separada) | ✅ |
| Compatibilidad hacia atrás | ✅ C# 3+ | C# 14+ únicamente |
← La palabra clave field en C# 14: adiós para siempre a los backing fields repetitivos | Serie .NET 8 → .NET 10 | Próximo: LINQ en .NET 9 y .NET 10: CountBy, AggregateBy, Index() y las mejoras que cambian cómo consultás datos →
Deja una respuesta