Continuando nossa exploração do tópico C #, traduzimos o seguinte artigo curto para você sobre o uso original de métodos de extensão. Recomendamos que você preste atenção especial à última seção sobre interfaces, assim como ao perfil do autor.

Tenho certeza de que qualquer pessoa com um pouco de experiência em C # está ciente da existência de métodos de extensão. Este é um bom recurso que permite aos desenvolvedores estender os tipos existentes com novos métodos.
Isso é extremamente útil nos casos em que você deseja adicionar funcionalidade a tipos que você não controla. Na verdade, qualquer um, mais cedo ou mais tarde, teve que escrever uma extensão para o BCL apenas para tornar as coisas mais acessíveis.
Mas, junto com casos de uso relativamente óbvios, também existem padrões muito interessantes vinculados diretamente ao uso de métodos de extensão e demonstrando como eles podem ser usados de uma forma menos tradicional.
Adicionando Métodos a Enumerações
Uma enumeração é simplesmente uma coleção de valores numéricos constantes, cada um com um nome exclusivo. Embora as enumerações em C # sejam herdadas da classe abstrata Enum, elas não são interpretadas como classes reais. Em particular, esta limitação impede que eles tenham métodos.
Em alguns casos, pode ser útil programar a lógica em uma enumeração. Por exemplo, se um valor de enumeração pode existir em várias visualizações diferentes e você deseja convertê-la facilmente em outra.
Por exemplo, imagine o seguinte tipo em um aplicativo típico que permite salvar arquivos em vários formatos:
public enum FileFormat
{
PlainText,
OfficeWord,
Markdown
}
Essa enumeração define uma lista de formatos suportados pelo aplicativo e pode ser usada em diferentes partes do aplicativo para iniciar a lógica de ramificação com base em um valor específico.
Uma vez que cada formato de arquivo pode ser representado como uma extensão de arquivo, seria bom se cada
FileFormat
um tivesse um método para obter essas informações. É com o método de extensão que isso pode ser feito, algo 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";
// , ,
//
throw new ArgumentOutOfRangeException(nameof(self));
}
}
O que, por sua vez, nos permite fazer isso:
var format = FileFormat.Markdown;
var fileExt = format.GetFileExtension(); // "md"
var fileName = $"output.{fileExt}"; // "output.md"
Refatorando classes de modelo Às
vezes, você não deseja adicionar um método diretamente a uma classe, por exemplo, se estiver trabalhando com um modelo anêmico .
Os modelos anêmicos são geralmente representados por um conjunto de propriedades públicas imutáveis, somente get. Portanto, ao adicionar métodos a uma classe de modelo, você pode ter a impressão de que a pureza do código foi violada ou pode suspeitar que os métodos se referem a algum tipo de estado privado. Os métodos de extensão não causam esse problema, uma vez que não têm acesso aos membros privados do modelo e não são, por natureza, parte do modelo.
Portanto, considere o seguinte exemplo com dois modelos, um representando uma lista de títulos fechada e o outro representando uma linha de títulos separada:
public class ClosedCaption
{
//
public string Text { get; }
//
public TimeSpan Offset { get; }
//
public TimeSpan Duration { get; }
public ClosedCaption(string text, TimeSpan offset, TimeSpan duration)
{
Text = text;
Offset = offset;
Duration = duration;
}
}
public class ClosedCaptionTrack
{
// ,
public string Language { get; }
//
public IReadOnlyList<ClosedCaption> Captions { get; }
public ClosedCaptionTrack(string language, IReadOnlyList<ClosedCaption> captions)
{
Language = language;
Captions = captions;
}
}
No estado atual, se precisarmos que a string de legenda seja exibida em um determinado momento, executaremos o LINQ assim:
var time = TimeSpan.FromSeconds(67); // 1:07
var caption = track.Captions
.FirstOrDefault(cc => cc.Offset <= time && cc.Offset + cc.Duration >= time);
Isso realmente requer algum tipo de método auxiliar que poderia ser implementado como um método de membro ou um método de extensão. Prefiro a segunda opção.
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);
}
Nesse caso, o método de extensão permite que você obtenha o mesmo que o usual, mas oferece uma série de bônus não óbvios:
- É claro que esse método funciona apenas com membros públicos da classe e não altera seu estado privado de alguma forma misteriosa.
- Obviamente, esse método apenas permite cortar o canto e é fornecido aqui apenas para sua conveniência.
- Este método pertence a uma classe completamente separada (ou mesmo assembly) cujo propósito é separar os dados da lógica.
Em geral, ao usar a abordagem do método de extensão, é conveniente traçar uma linha entre o necessário e o útil.
Tornando as interfaces versáteis
Ao projetar uma interface, você sempre deseja que o contrato seja mantido o menor possível, pois torna-o mais fácil de implementar. Ajuda muito quando a interface fornece funcionalidade da maneira mais generalizada, para que seus colegas (ou você) possam construir sobre ela para lidar com casos mais específicos.
Se isso parece um absurdo para você, considere uma interface típica que salva um modelo em um arquivo:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
}
Tudo funciona bem, mas em algumas semanas um novo requisito pode chegar com o tempo: as classes que implementam
IExportService
devem não apenas exportar para um arquivo, mas também ser capazes de gravar em um arquivo.
Portanto, para cumprir esse requisito, adicionamos um novo método ao contrato:
public interface IExportService
{
FileInfo SaveToFile(Model model, string filePath);
byte[] SaveToMemory(Model model);
}
Essa mudança quebrou todas as implementações existentes
IExportService
, pois agora todas elas precisam ser atualizadas para oferecer suporte à gravação na memória.
Mas, para não fazer tudo isso, poderíamos ter projetado a interface um pouco diferente desde o início:
public interface IExportService
{
void Save(Model model, Stream output);
}
Nesse formulário, a interface força você a escrever o destino da forma mais generalizada, ou seja, este
Stream
. Agora não estamos mais limitados a arquivos durante o trabalho e também podemos direcionar várias outras opções de saída.
A única desvantagem dessa abordagem é que as operações mais básicas não são tão simples como estamos acostumados: agora temos que definir uma instância específica
Stream
, envolvê-la em uma instrução using e passá-la como um parâmetro.
Felizmente, essa desvantagem é completamente anulada ao usar 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 original, nós a tornamos muito mais versátil e não sacrificamos a usabilidade usando métodos de extensão.
Assim, considero os métodos de extensão uma ferramenta inestimável para manter o simples e simples e transformar o complexo em possível .