CodeGym /Cursos /C# SELF /Extension Members: métodos

Extension Members: métodos

C# SELF
Nível 18 , Lição 2
Disponível

1. Introdução

No mundo da programação orientada a objetos, a gente frequentemente precisa adicionar funcionalidades novas a classes que já existem. Às vezes, não temos acesso ao código-fonte dessas classes (tipo, podem ser tipos padrão do .NET, classes de libs de terceiros ou código gerado automaticamente).

Nessas horas, os Extension Methods salvam a pátria — é um recurso poderoso e elegante do C# que permite "fingir" que você está adicionando métodos novos a tipos já existentes, sem mexer neles diretamente.

Extension methods são um açúcar sintático do C# que deixa você chamar métodos estáticos como se fossem métodos de instância do tipo. Eles parecem e são usados como se sempre tivessem feito parte do tipo, mas na real não é assim.

Por exemplo, digamos que você quer um método que deixa a primeira letra de uma string maiúscula:


public static class StringUtil
{
    public static string CapitalizeFirstLetter(string str)
    {
        if (string.IsNullOrEmpty(str)) return str;
        return char.ToUpper(str[0]) + str.Substring(1);
    }
}

// uso
string hello = StringUtil.CapitalizeFirstLetter("privet"); // "Privet"
Console.WriteLine(hello); 

O código é ok, mas o C# deixa isso bem mais compacto.

A linha

string hello = StringUtil.CapitalizeFirstLetter("privet"); // "Privet"

pode ser escrita assim:

string hello = "privet".CapitalizeFirstLetter(); // "Privet"

E agora eu vou te mostrar como isso funciona.

2. Como Extension Methods funcionam

Apesar de Extension Methods parecerem mágica, na real eles são bem simples. Extension method nada mais é do que um método estático normal, declarado numa classe estática, mas com um detalhe importante: o primeiro parâmetro é marcado com a palavra-chave this. É esse parâmetro que diz pra qual tipo o método "se aplica" como extensão.

Vamos ver um exemplo de extensão do tipo string:

namespace MyProject.Extensions
{
    // 1. Extension Methods precisam ser declarados numa classe estática.
    public static class StringExtensions
    {
        // 2. O método tem que ser estático.
        // 3. O primeiro parâmetro do método tem que ser marcado com 'this'.
        public static string CapitalizeFirstLetter(this string str)
        {
            if (string.IsNullOrEmpty(str)) 
                return str; 

            // Usando String.ToUpper e String.Substring pra criar uma nova string
            return char.ToUpper(str[0]) + str.Substring(1);
        }

        // Mais um exemplo:
        public static int WordCount(this string text)
        {
            if (string.IsNullOrEmpty(text))
                return 0;

            // Conta palavras separando por espaço
            return text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}    

Agora, pra usar esses métodos, é só importar o namespace MyProject.Extensions no seu arquivo:

using MyProject.Extensions; // Importante: importar o namespace onde estão os Extension Methods

string hello = "privet, mir!";
Console.WriteLine(hello.CapitalizeFirstLetter()); // Vai mostrar: "Privet, mir!"

string sentence = "Eto testovaya stroka dlya podscheta slov.";
Console.WriteLine($"Kolichestvo slov: {sentence.WordCount()}"); // Vai mostrar: "Kolichestvo slov: 6"

string emptyString = "";
Console.WriteLine(emptyString.CapitalizeFirstLetter()); // Vai mostrar: ""

Importante: Se a palavra-chave this não estivesse antes do primeiro parâmetro string str, CapitalizeFirstLetter seria só um método estático normal da classe StringExtensions, e você teria que chamar assim: StringExtensions.CapitalizeFirstLetter("privet"). É o this que transforma ele num Extension Method, deixando você chamar no estilo OO.

3. Sintaxe dos Extension Methods

Pra criar e usar Extension Methods, segue esses passos:

  1. Crie uma classe estática: Todos os Extension Methods têm que estar dentro de uma classe estática. O nome pode ser qualquer um, mas o padrão é chamar de [TypeName]Extensions (tipo StringExtensions, ListExtensions, DateTimeExtensions).
  2. Declare um método estático: Dentro da sua classe estática, escreva um método estático normal.
  3. Marque o primeiro parâmetro com this: O mais importante — o primeiro parâmetro do seu método estático tem que ser marcado com this, seguido do tipo que você quer estender. Esse parâmetro vai ser a instância do objeto pra quem você chama o Extension Method.
  4. Importe o namespace: No arquivo onde você quer usar o Extension Method, garante que você importou o namespace (using) onde está sua classe estática com os Extension Methods. Sem isso, o compilador não acha suas extensões.
  5. Use como método normal: Agora você pode chamar seu Extension Method como se fosse um método de instância dos objetos daquele tipo.

4. Como a "mágica" realmente funciona

A "mágica" dos Extension Methods é graças ao compilador C#. Em tempo de execução, Extension Methods não fazem parte dos métodos da classe. O compilador C# transforma a chamada do Extension Method em uma chamada estática normal.

Quando você escreve obj.Method(args), onde Method é um Extension Method, o compilador na hora de compilar troca isso por ContainingClass.Method(obj, args). Isso acontece por baixo dos panos, criando a ilusão de que o método faz parte da classe.

Resolvendo conflitos: Extension Methods vs Métodos Normais

O que acontece se a classe já tem um método "nativo" (de instância) com o mesmo nome e assinatura do seu Extension Method?

public class MyClass
{
    public void DoSomething(int value)
    {
        Console.WriteLine($"Rodnoy metod MyClass.DoSomething: {value}");
    }
}

public static class MyClassExtensions
{
    public static void DoSomething(this MyClass instance, int value)
    {
        Console.WriteLine($"Extension Method MyClassExtensions.DoSomething: {value}");
    }
}

// Uso:
MyClass obj = new MyClass();
obj.DoSomething(10); // Vai chamar o método nativo MyClass.DoSomething: 10

Regra de prioridade: Se a classe já tem um método de instância com o mesmo nome e assinatura (quantidade e tipos de parâmetros), sempre vai ser chamado o método de instância nativo, não o Extension Method. Extension Methods têm prioridade menor quando rola conflito de nomes.

Limitações dos Extension Methods

  • Não dá pra sobrescrever (override) métodos existentes: Extension Methods não participam de polimorfismo.
  • Não dá pra adicionar novos campos, propriedades ou eventos: Só dá pra adicionar métodos. Não dá pra usar eles pra adicionar estado novo ao tipo.
  • Não dá pra estender classes estáticas: O primeiro parâmetro this sempre tem que ser uma instância (objeto) do tipo.
  • Não dá pra estender campos ou propriedades: Só tipos podem ser estendidos.
  • Sem acesso a membros privados ou protegidos: Extension Methods só conseguem acessar membros public do tipo que estão estendendo.

Mas você pode adicionar Extension Methods para:

  • Interfaces: Isso é muito massa! Você pode adicionar métodos pra todos os tipos que implementam uma interface, sem mexer na interface ou nas classes. Por exemplo, todo mundo que implementa IEnumerable<T> "ganha" os Extension Methods do LINQ.
  • O tipo object: Isso deixa você criar Extension Methods que ficam disponíveis pra qualquer objeto em C#. Mas usa isso com cuidado, pra não poluir o namespace global nem criar conflitos de nomes.

5. Exemplos reais e Best Practices

Extension Methods não são só teoria, são uma ferramenta poderosa pro dia a dia. Olha só alguns cenários práticos:

1. Estendendo DateTime:

Adicionando métodos úteis pra trabalhar com datas, que são comuns na lógica de negócio.

using System;

namespace MyProject.TimeHelpers
{
    public static class DateTimeExtensions
    {
        public static bool IsWeekend(this DateTime date)
        {
            return date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
        }

        public static bool IsWeekday(this DateTime date)
        {
            return !date.IsWeekend(); // Usando outro Extension Method!
        }

        public static DateTime NextDay(this DateTime date)
        {
            return date.AddDays(1);
        }

        public static DateTime AddBusinessDays(this DateTime date, int days)
        {
            DateTime newDate = date;
            while (days > 0)
            {
                newDate = newDate.NextDay();
                if (newDate.IsWeekday())
                {
                    days--;
                }
            }
            return newDate;
        }
    }
}

Uso:

using MyProject.TimeHelpers;

DateTime today = DateTime.Now;
if (today.IsWeekend())
{
    Console.WriteLine("Segodnya vykhodnoy!");
}
else
{
    Console.WriteLine("Segodnya budniy den.");
}

DateTime tomorrow = today.NextDay();
Console.WriteLine($"Zavtra: {tomorrow.ToShortDateString()}");

DateTime futureDate = today.AddBusinessDays(5);
Console.WriteLine($"Cherez 5 rabochikh dney budet: {futureDate.ToShortDateString()}");

2. Estendendo seu próprio tipo (tipo Dog):

Mesmo que você tenha acesso ao código da classe, Extension Methods podem ser úteis pra separar lógica auxiliar ou pra seguir o princípio da responsabilidade única (Single Responsibility Principle).

namespace MyProject.Animals
{
    public class Dog
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Breed { get; set; }
    }

    public static class DogExtensions
    {
        public static bool IsPuppy(this Dog dog)
        {
            // Definição de filhote
            return dog.Age < 2;
        }

        public static string GetAgeCategory(this Dog dog)
        {
            if (dog.Age < 1) return "Shchenok";
            if (dog.Age < 7) return "Vzroslaya sobaka";
            return "Pozhilaya sobaka";
        }

        public static void Bark(this Dog dog)
        {
            Console.WriteLine($"{dog.Name} govorit: Gav! Gav!");
        }
    }
}

Uso:


using MyProject.Animals;

var sharik = new Dog { Name = "Sharik", Age = 1, Breed = "Dvornyaga" };
if (sharik.IsPuppy())
{
    Console.WriteLine($"{sharik.Name} — eshche shchenok!"); // Sharik — eshche shchenok!
}

Console.WriteLine($"{sharik.Name} v kategorii: {sharik.GetAgeCategory()}"); // Sharik v kategorii: Shchenok
sharik.Bark(); // Sharik govorit: Gav! Gav!

var rex = new Dog { Name = "Reks", Age = 5, Breed = "Ovcharka" };
Console.WriteLine($"{rex.Name} v kategorii: {rex.GetAgeCategory()}"); // Reks v kategorii: Vzroslaya sobaka

Best Practices:

Escolha nomes significativos: Os nomes das classes de extensão (StringExtensions, ListExtensions) e dos métodos devem ser claros e mostrar o que eles fazem.

Coloque em namespaces lógicos: Agrupe Extension Methods em namespaces que fazem sentido, pra não poluir o namespace global.

Use com moderação: Não exagere nos Extension Methods. Se você pode adicionar o método direto na classe, talvez seja melhor. Extension Methods são melhores pra adicionar funcionalidade não intrusiva.

Evite conflitos de nomes: Lembre da prioridade dos métodos nativos. Se seu Extension Method tem o mesmo nome e assinatura de um método já existente, ele não vai ser chamado.

Mantenha o método limpo: Extension Methods devem fazer uma tarefa clara e focada. Não complica demais.

Testabilidade: Extension Methods são métodos estáticos normais, então são bem fáceis de testar.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION