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:
- 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).
- Declare um método estático: Dentro da sua classe estática, escreva um método estático normal.
- 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.
- 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.
- 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.
GO TO FULL VERSION