← maurobernal.com.ar

Etiqueta: extensions

  • Extension Members en C# 14/15: propiedades y miembros estáticos de extensión como nunca antes

    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ísticaMé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 →

Tags

tsql (27)mssql (26)sql (20)devops (20)dotnet (18)docker (15)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (7)angular (7)angular21 (7)sql server (6)issabel (6)docker-compose (6)typescript (6)mysql (5).NET (5)