Os métodos de extensão são uma parte fundamental do C# e da Programação Orientada a Objetos (OOP). Os 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 que não pode alterar, como tipos de bibliotecas de terceiros ou tipos embutidos do .NET, como string, List<T>, e assim por diante.

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

Índice

Como Criar Métodos de Extensão de DateTime

Vamos supor que desejamos 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 fim 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 está sendo estendido.

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

  1. Não é necessário um objeto: 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 os 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 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 retornará um valor bool (true/false).

this DateTime date: A palavra-chave this como um argumento do método indica que este método é um método de extensão. 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 possa ser encadeado com outros, ele geralmente 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 conforme abaixo, o que converterá a string para maiúsculas antes de remover todos os espaços em branco e anexar a string fornecida.

Observe que fornecemos apenas o segundo parâmetro para o método TrimAndAppend, pois o primeiro parâmetro é a string à qual o método de extensão foi aplicado (conforme explicado anteriormente, indicado 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 motivos menos óbvios, isso não funcionará. Quando você encadeia métodos, eles não se conectam à variável original, mas ao tipo de retorno da chamada do método anterior.

Aqui, temos uma data chamada IsWeekend() que retorna um valor Booleano. Em seguida, tentamos chamar AddDays(1) em um valor Booleano que não existe, pois é uma extensão de DateTime. O compilador de código falhará ao construir, levantando um erro informando sobre 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 tomar 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 gama 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 novos itens à 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 poderíamos encadear os métodos. Ainda assim, ele removeria as duplicatas, mas ações adicionais como adicionar novos itens não seriam possíveis na mesma expressão.

Também teríamos que atualizar o RemoveDuplicates para atualizar o argumento da lista passada, já que Distinct() retorna uma nova lista que não está sendo retornada como mostrado abaixo, o que, eu acredito que você concordará, é muito 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 central 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 o inchaço da classe com métodos que podem não ser usados com frequência.

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

Vamos supor que você esteja 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, pois ela pertence ao namespace System.IO (ou seja, está incorporada no .Net).

Sem 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 em 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 e pacotes de terceiros pode tornar seu código muito mais compatível.

Melhor Organização & Legibilidade: Você pode organizar métodos de extensão em classes estáticas com base na funcionalidade ou contexto, facilitando a localização e o 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 próprio tipo (por exemplo, formatação, validação).

  • Para Aprimorar Tipos Incorporados: Se você deseja adicionar funcionalidades aos tipos incorporados (como string ou DateTime) sem modificá-los.

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

Cenário de Exemplo

Imagine que você tenha uma classe Person e deseje 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, ao mesmo tempo que fornece funcionalidades úteis.

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

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

  • Para Acoplamento Rígido: 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 conforme 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 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 'engineNumber' privado
        // Isso resultará em um erro de tempo de compilação.
        Console.WriteLine(car.engineNumber);
    }
}

Duplicação e uso excessivo de código

  • Em alguns casos, os métodos de extensão podem encorajar a duplicação de código. Se vários 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 forma eficaz. Eu recomendaria manter todas as extensões dentro de uma pasta ou projeto de extensões, próximo à origem (dependendo dos padrões de design usados em sua aplicação).

  • Abuso de Extensões: Se forem usadas em excesso, 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 auxiliares separadas ou serviços 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 substituto para funcionalidades que pertencem à classe em si.