Tenho certeza de que todos com pelo menos alguns antecedentes em C# estão cientes dos métodos de extensão – um bom recurso que permite que os desenvolvedores estendam os tipos existentes com novos métodos.
Isso é extremamente conveniente, caso você queira adicionar funcionalidade aos tipos que você não tem controle. Na verdade, acho que todo mundo em algum momento é autor de extensões para a biblioteca da classe base apenas para tornar algumas coisas mais acessíveis.
Mas, além dos casos de uso mais óbvios, existem alguns outros padrões interessantes que confiam diretamente nos métodos de extensão e mostram como eles podem ser usados de uma maneira um pouco menos convencional.
Adicionando métodos a enums
Uma enumeração é simplesmente um conjunto de valores numéricos constantes com nomes atribuídos exclusivamente a eles. Mesmo que todos os enumes em C# herdem do Enum
Classe abstrata, eles não são realmente tratados como classes. Essa limitação, entre outras coisas, impede que eles tenham métodos.
Existem alguns casos em que ter lógica em uma enumeração pode ser útil. Um desses casos é onde um valor de uma enumeração pode ter várias representações diferentes e você deseja converter facilmente entre elas.
Por exemplo, imagine o seguinte tipo em um aplicativo genérico que pode salvar arquivos em vários formatos:
public enum FileFormat
{
PlainText,
OfficeWord,
Markdown
}
Este enum define uma lista de formatos que o aplicativo suporta e provavelmente é usado em vários locais do código para invocar a lógica de ramificação, dependendo do seu valor.
Como cada formato de arquivo também pode ser representado por sua extensão de arquivo, seria bom se FileFormat
continha um método para obtê -lo. É aqui que podemos usar um método de extensão para fazer assim:
public static class FileFormatExtensions
{
public static string GetFileExtension(this FileFormat self)
{
if (self == FileFormat.PlainText)
return "txt";
if (self == FileFormat.OfficeWord)
return "docx";
if (self == FileFormat.Markdown)
return "md";
// This will be thrown if we add a new file format
// but forget to add the corresponding file extension.
throw new ArgumentOutOfRangeException(nameof(self));
}
}
Que por sua vez nos permite fazer o seguinte:
var format = FileFormat.Markdown;
var fileExt = format.GetFileExtension(); // "md"
var fileName = $"output.{fileExt}"; // "output.md"
Refatoração de classes de modelos
Há casos em que você pode não querer adicionar um método diretamente a uma classe, por exemplo, quando é um modelo anêmico.
Os modelos anêmicos são tipicamente representados por um conjunto de propriedades públicas apenas para obter obter propriedades imutáveis; portanto, adicionar métodos a uma classe de modelo pode fazer com que pareça impuro ou pode dar uma impressão de que os métodos estão acessando algum estado privado. Os métodos de extensão não têm esse problema porque não podem acessar os membros privados do modelo e, por natureza, não fazem parte do próprio modelo.
Portanto, considere este exemplo de dois modelos – um representa uma faixa de legenda fechada para um vídeo e outro representa uma legenda individual:
public class ClosedCaption
{
// Text that gets displayed
public string Text { get; }
// When it gets displayed relative to the beginning of the track
public TimeSpan Offset { get; }
// How long it stays on the screen
public TimeSpan Duration { get; }
public ClosedCaption(string text, TimeSpan offset, TimeSpan duration)
{
Text = text;
Offset = offset;
Duration = duration;
}
}
public class ClosedCaptionTrack
{
// Language of the closed captions inside
public string Language { get; }
// Collection of closed captions
public IReadOnlyList<ClosedCaption> Captions { get; }
public ClosedCaptionTrack(string language, IReadOnlyList<ClosedCaption> captions)
{
Language = language;
Captions = captions;
}
}
No estado atual, se alguém quisesse ter uma legenda fechada exibida em um momento específico, teria que executar um LINQ como este:
var time = TimeSpan.FromSeconds(67); // 1:07
var caption = track.Captions
.FirstOrDefault(cc => cc.Offset <= time && cc.Offset + cc.Duration >= time);
Isso realmente exige um método auxiliar de algum tipo que possa ser implementado como um método de membro ou um método de extensão. Pelas razões explicadas anteriormente, eu pessoalmente prefiro o último:
public static class ClosedCaptionTrackExtensions
{
public static ClosedCaption GetByTime(this ClosedCaptionTrack self, TimeSpan time) =>
self.Captions.FirstOrDefault(cc => cc.Offset <= time && cc.Offset + cc.Duration >= time);
}
Um método de extensão aqui alcança a mesma coisa que um método normal, mas oferece alguns benefícios sutis:
- É claro que esse método só funciona com membros públicos da classe e não sofre seu estado privado de alguma maneira obscura
- É óbvio que esse método simplesmente fornece um atalho e existe apenas por conveniência
- Este método faz parte de uma classe totalmente separada (ou potencialmente até outra montagem) que nos ajuda a isolar dados da lógica
No geral, o uso de uma abordagem do método de extensão aqui ajuda a desenhar a linha entre o que está necessário E o que é útil.
Tornando as interfaces mais versáteis
Ao projetar uma interface, você sempre deseja manter o contrato mínimo para facilitar a implementação. Ajuda muito quando sua interface expõe o tipo mais genérico de funcionalidade para que outros (ou você) possam construir sobre ela e cobrir casos mais específicos.
Se isso não fez muito sentido, aqui está um exemplo de uma interface típica que salva algum modelo em um arquivo:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
}
Funciona bem, mas algumas semanas depois você volta a ele com um novo requisito – aulas implementando IExportService
além de exportar para um arquivo, agora também deve ser capaz de gravar na memória.
Portanto, para satisfazer esse requisito, você adiciona um novo método ao contrato:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
byte() SaveToMemory(Model model);
}
Essa mudança acabou de quebrar todas as implementações existentes de IExportService
Porque agora todos eles precisam ser atualizados para apoiar a escrita na memória também.
Em vez de fazer tudo isso, poderíamos ter projetado a versão inicial da interface um pouco diferente:
public interface IExportService
{
void Save(Model model, Stream output);
}
Dessa forma, a interface aplica a escrita para o destino mais genérico, um Stream
. Agora, não estamos mais limitados aos arquivos e também podemos atingir uma variedade de saídas diferentes.
A única desvantagem dessa abordagem é que as operações mais básicas não são tão diretas quanto costumavam ser – agora você precisa configurar uma instância concreta de um Stream
embrulhe -o em um using
declaração, e depois passá -lo como um argumento.
Felizmente, essa desvantagem pode ser completamente negada com o uso de métodos de extensão:
public static class ExportServiceExtensions
{
public static FileInfo SaveToFile(this IExportService self, Model model, string filePath)
{
using (var output = File.Create(filePath))
{
self.Save(model, output);
return new FileInfo(filePath);
}
}
public static byte() SaveToMemory(this IExportService self, Model model)
{
using (var output = new MemoryStream())
{
self.Save(model, output);
return output.ToArray();
}
}
}
Ao refatorar a interface inicial, a tornamos muito mais versátil e sustentável e, graças aos métodos de extensão, não tivemos que sacrificar a usabilidade de forma alguma.
Em suma, acho que os métodos de extensão são uma ferramenta inestimável que pode ajudar mantenha coisas simples simples e torne as coisas complexas possíveis.

Luis es un experto en Inteligência Empresarial, Redes de Computadores, Gestão de Dados e Desenvolvimento de Software. Con amplia experiencia en tecnología, su objetivo es compartir conocimientos prácticos para ayudar a los lectores a entender y aprovechar estas áreas digitales clave.