Métodos de extensão são uma parte fundamental do C# e da Programação Orientada a Objetos (POO). Métodos de extensão em C# permitem que você “estenda” tipos existentes, incluindo classes, interfaces ou structs, sem modificar seu código original.

Isso é particularmente útil quando você deseja adicionar nova funcionalidade a um tipo que não possui ou não pode alterar, como tipos de bibliotecas de terceiros ou tipos embutidos do .NET, como string, List<T>, e assim por diante.

No artigo de hoje, você aprenderá como adicionar métodos de extensão às suas classes, bem como a classes de terceiros e do sistema.

Índice

Como Criar Métodos de Extensão de DateTime

Digamos que queremos alguns métodos que possam ser usados juntamente com a classe DateTime existente, talvez um método que retorne se o objeto DateTime fornecido é um final de semana ou algo diferente.

Métodos de extensão precisam ser definidos em uma classe estática porque são essencialmente açúcar sintático que permite chamar um método estático como se fosse um método de instância no tipo que você está estendendo.

Métodos de extensão precisam estar em uma classe estática porque:

  1. Nenhum Objeto Necessário: Você não precisa criar um objeto para usar um método de extensão. Como o método adiciona nova funcionalidade a um tipo existente (como string), ele pode funcionar sem precisar de uma instância da classe.

  2. Código Organizado: Colocar métodos de extensão em uma classe estática mantém as coisas organizadas. Isso permite agrupar métodos relacionados e você pode facilmente incluí-los em seu código usando o namespace apropriado.

Portanto, ao usar uma classe estática, você pode adicionar métodos úteis a tipos existentes sem alterar seu código original, e não precisa de um objeto para chamá-los.

Primeiro, vamos criar uma classe estática DateTimeExtensions.

public static class DateTimeExtensions {

}

Isso vai abranger todas as extensões de DateTime que queremos criar.

public static bool IsWeekend(this DateTime date)
{
    return date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}

Explicação:

public static bool IsWeekend: Isso define que é um método estático chamado IsWeekend que irá retornar um valor bool (verdadeiro/falso).

this DateTime date: A palavra-chave this como um argumento de método denota que esse método é um método de extensão. Isso significa que o método será uma extensão da classe DateTime.

Como Encadear Métodos de Extensão do Mesmo Tipo

Para que um método de extensão seja encadeado com outros, ele normalmente precisa retornar o mesmo tipo que está estendendo (ou um tipo compatível). Isso permite que outro método seja chamado no resultado do anterior.

using System.Globalization;

public static string ToTitleCase(this string str)
{
    return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower());
}

public static string TrimAndAppend(this string str, string toAppend)
{
    return str.Trim() + toAppend;
}

No exemplo acima, tanto os métodos ToTitleCase quanto TrimAndAppend retornam um valor de string, o que significa que podemos encadear os métodos de extensão como abaixo, o que converterá a string para o formato de título antes de remover todos os espaços em branco e anexar a string fornecida.

Observe que fornecemos apenas o segundo parâmetro ao método TrimAndAppend, pois o primeiro parâmetro é a string na qual o método de extensão está sendo aplicado (como explicado anteriormente, denotado pela palavra-chave this).

var title = "hello world   "
    .ToTitleCase()
    .TrimAndAppend("!!");

//Saída:
// Olá Mundo!!

Se o método de extensão retornar um tipo diferente (não o original ou um tipo compatível), você não pode encadeá-lo. Por exemplo:

var date = new DateTime();
date.IsWeekend().AddDays(1);

Por razões menos óbvias, isso não funcionará. Quando você encadeia métodos, eles não se encadeiam a partir da variável original—eles se encadeiam a partir do tipo de retorno da chamada do método anterior.

Aqui, temos uma data chamada IsWeekend() que retorna um Booleano. Em seguida, tentamos chamar AddDays(1) em um valor Booleano que não existe, pois é uma extensão DateTime. O compilador de código falhará ao compilar, gerando um erro informando isso.

Como Retornar a Instância para Encadear

Em alguns métodos de extensão, especialmente aqueles para configuração (como Injeção de Dependência), você retorna a mesma instância para permitir o encadeamento de métodos. Isso permite que você continue trabalhando com o objeto original ou seu estado modificado em várias chamadas, possibilitando uma interface fluente.

Vamos pegar o exemplo de uma lista de carros.

public static List<T> RemoveDuplicates<T>(this List<T> list)
{
    // Use Distinct para remover duplicatas e atualizar a lista
    list = list.Distinct().ToList();

    // Retorne a lista modificada para permitir o encadeamento de métodos
    return list;
}

public static List<T> AddRangeOfItems<T>(this List<T> list, IEnumerable<T> items)
{
    // Adicione uma faixa de itens à lista
    list.AddRange(items);

    // Retorne a lista modificada para permitir o encadeamento de métodos
    return list;  
}

Agora que retornamos a lista desses métodos de extensão, podemos encadear métodos adicionais na mesma lista. Por exemplo, após remover duplicatas com RemoveDuplicates(), podemos imediatamente chamar AddRangeOfItems() na mesma lista.

Então, podemos fazer algo como:

var existingStock = new List<string> { "Ford", "Jaguar", "Ferrari", "Ford", "Renault" };

var availableBrands = existingStock
    .RemoveDuplicates()
    .AddRangeOfItems(new[] { "Lamborghini" }); // novo estoque disponível

Console.WriteLine("Brands Available Now: " + string.Join(", ", availableBrands));

// Saída: Marcas Disponíveis Agora: Ford, Jaguar, Ferrari, Renault, Lamborghini

Removemos duplicatas de uma lista de marcas de carros e adicionamos novo estoque à mesma lista. Isso funciona porque RemoveDuplicates retorna a lista, nos permitindo encadeá-la com AddRangeOfItems.

Se RemoveDuplicates retornasse void em vez da lista, não seríamos capazes de encadear os métodos. Ainda assim removeria duplicatas, mas ações adicionais como adicionar novo estoque não seriam possíveis na mesma expressão.

Também teríamos que atualizar o RemoveDuplicates para atualizar o argumento da lista passada, pois Distinct() retorna uma nova lista que não está sendo retornada como mostrado abaixo, o que acredito que concordará que é bem mais verboso.

public static void RemoveDuplicates<T>(this List<T> list)
{
    // Obter os elementos distintos e limpar a lista original
    var distinctItems = list.Distinct().ToList();
    list.Clear(); 

    // Adicionar os itens distintos de volta à lista original
    list.AddRange(distinctItems);
}

Por Que Não Posso Apenas Adicionar Esses Métodos à Minha Classe?

Se o método não é uma parte essencial da funcionalidade da classe, colocá-lo em um método de extensão pode ajudar a manter a classe focada e mantida.

Separação de Preocupações: Usar métodos de extensão mantém seu código mais limpo e ajuda a reduzir a complexidade. Isso ajuda a evitar inflar a classe com métodos que podem não ser utilizados com frequência.

Melhorando Bibliotecas Externas: Se você está usando uma biblioteca ou framework onde não pode modificar o código-fonte, métodos de extensão permitem adicionar funcionalidades a esses tipos sem alterar suas definições.

Vamos supor que você está usando a classe FileInfo do namespace System.IO para trabalhar com arquivos. Você pode querer adicionar um método para verificar facilmente se um arquivo é muito grande (por exemplo, mais de 1 GB), mas não pode modificar a classe FileInfo diretamente porque ela pertence ao namespace System.IO (ou seja, está embutida no .Net).

Sem uma extensão:

var fileInfo = new FileInfo("myFile.txt");

if (fileInfo.Length > 1024 * 1024 * 1024) // o tamanho do arquivo é maior que 1GB
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Com Método de Extensão:

Você pode tornar isso mais reutilizável adicionando um método de extensão que verifica se o arquivo é maior que 1 GB.

public static class FileInfoExtensions
{
    // método de extensão, com tamanho de arquivo padrão de 1GB (pode ser substituído)
    public static bool IsFileTooLarge(this FileInfo fileInfo, long sizeInBytes = 1024 * 1024 * 1024)
    {
        return fileInfo.Length > sizeInBytes;
    }
}

Agora você pode usar o método IsFileTooLarge diretamente nos objetos FileInfo, tornando seu código mais limpo:

csharpCopy codevar fileInfo = new FileInfo("myFile.txt");

if (fileInfo.IsFileTooLarge())
{
    Console.WriteLine("The file is too large.");
}
else
{
    Console.WriteLine("The file size is acceptable.");
}

Estender bibliotecas de terceiros pode tornar seu código muito mais compatível.

Melhor Organização e Legibilidade: Você pode organizar métodos de extensão em classes estáticas com base na funcionalidade ou contexto, facilitando a localização e uso deles. Isso é certamente aprimorado permitindo que métodos de extensão sejam encadeados.

Quando Usar Extensões

  • Para Métodos de Utilidade: Se você tiver métodos de utilidade que são úteis para um tipo, mas não pertencem diretamente ao tipo em si (por exemplo, formatação, validação).

  • Para Melhorar Tipos Embutidos: Se você deseja adicionar funcionalidade a tipos embutidos (como string ou DateTime) sem modificá-los.

  • Quando Você Quer Manter Métodos Opcionais: Se você deseja fornecer métodos adicionais que os usuários podem optar por usar sem forçá-los a incorporá-los ao design principal da classe.

Cenário de Exemplo

Imagine que você tem uma classe Person, e deseja adicionar um método para formatar o nome da pessoa de forma agradável:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

// Método de extensão em uma classe estática
public static class PersonExtensions
{
    public static string GetFullName(this Person person)
    {
        return $"{person.FirstName} {person.LastName}";
    }
}

Ao usar um método de extensão para GetFullName, você pode manter a classe Person simples e focada em suas responsabilidades principais, enquanto ainda fornece funcionalidade útil.

Quando Não Usar Métodos de Extensão

  • Para Funcionalidade Principal: Se um método é essencial para o comportamento central de uma classe, ele deve fazer parte da própria classe, e não ser uma extensão.

  • Para Acoplamento Apertado: Se o método de extensão requer conhecimento íntimo do estado privado da classe ou precisa de acesso regular à sua lógica interna.

  • Para APIs Públicas: Ao projetar uma biblioteca ou API voltada para o público, muitas vezes é melhor incluir métodos necessários diretamente na classe, em vez de forçar os usuários a encontrar ou criar seus métodos de extensão.

Coisas a Considerar ao Projetar Extensões

Embora os métodos de extensão sejam poderosos e convenientes em muitos casos, existem certos contras ou situações em que usá-los pode não ser a melhor escolha:

Comportamento Oculto/Confusão

  • Os métodos de extensão não aparecem diretamente na definição da classe, o que significa que podem ser mais difíceis de descobrir por desenvolvedores que não estão familiarizados com as extensões disponíveis.

  • Os desenvolvedores precisam saber que esses métodos de extensão existem, ou podem deixar de usá-los, a menos que estejam trabalhando em um IDE com recursos como IntelliSense (por exemplo, Visual Studio, JetBrains Rider). Esses IDEs podem sugerir métodos de extensão de outros arquivos ou namespaces à medida que detectam o tipo apropriado. Sem um IDE rico em recursos, o desenvolvedor teria que estar ciente dos métodos de extensão ou encontrar a pasta onde estão armazenados.

Não é possível acessar membros privados

  • Os métodos de extensão só podem acessar membros (métodos, propriedades, campos) que são públicos ou internos.

  • Não podem acessar membros privados ou protegidos de uma classe porque os métodos de extensão operam como se fossem parte da classe do lado de fora, semelhante a chamadas de método regulares de fora da classe.

Exemplo:

public class Car
{
    private string engineNumber = "12345"; // Campo privado

    public string Brand { get; set; } = "Ford"; // Propriedade pública

    private void StartEngine() // Método privado
    {
        Console.WriteLine("Engine started");
    }
}
public static class CarExtensions
{
    public static void DisplayBrand(this Car car)
    {
        Console.WriteLine($"Brand: {car.Brand}"); // Acessando a propriedade pública 'Marca'
    }

    public static void TryAccessPrivateField(this Car car)
    {
        // Não é possível acessar o 'número do motor' privado
        // Isso resultará em um erro de compilação.
        Console.WriteLine(car.engineNumber);
    }
}

Duplicação de Código & Uso Excessivo

  • Em alguns casos, métodos de extensão podem incentivar a duplicação de código. Se múltiplos projetos ou classes requerem métodos de extensão semelhantes, você pode acabar escrevendo ou copiando os mesmos métodos de extensão em diferentes lugares, tornando mais difícil gerenciar e atualizar o código de forma consistente.

    Para evitar isso, organize seu código de maneira eficaz. Eu recomendo manter todas as extensões dentro de uma pasta ou projeto de extensões, próximo à origem (dependendo dos padrões de design sendo usados em sua aplicação).

  • Abuso de Extensões: Se usado excessivamente, elas podem poluir o espaço global com métodos que talvez não precisem ser globais. Isso pode causar poluição na API do tipo, tornando mais difícil entender o que é essencial para a classe versus o que é adicionado por meio de extensões.

Em alguns casos, é melhor encapsular a funcionalidade em classes ou serviços auxiliares separados em vez de adicioná-la por meio de métodos de extensão.

Conclusão

Os métodos de extensão são úteis para adicionar funcionalidades de forma limpa e modular, mas também podem introduzir confusão, conflitos de namespace e falta de acesso a membros privados.

Como destacado ao longo do artigo, eles têm muitos usos e são certamente um recurso muito bom do framework Dotnet quando usados de forma eficaz. Eles devem ser usados quando apropriado, mas não como um substituto para funcionalidades que pertencem à própria classe.