Método de extensão - Extension method

Na programação de computador orientada a objetos , um método de extensão é um método adicionado a um objeto depois que o objeto original foi compilado . O objeto modificado geralmente é uma classe, um protótipo ou um tipo. Os métodos de extensão são recursos de algumas linguagens de programação orientadas a objetos. Não há diferença sintática entre chamar um método de extensão e chamar um método declarado na definição de tipo.

No entanto, nem todas as linguagens implementam métodos de extensão de maneira igualmente segura. Por exemplo, linguagens como C #, Java (via Manifold) e Kotlin não alteram a classe estendida de forma alguma, porque isso pode quebrar hierarquias de classes e interferir no envio de métodos virtuais. É por isso que essas linguagens implementam estritamente métodos de extensão estaticamente e usam despacho estático para invocá-los.

Suporte em linguagens de programação

Os métodos de extensão são recursos de várias linguagens, incluindo C # , Java via Manifold , Gosu , JavaScript , Oxygene , Ruby , Smalltalk , Kotlin , Dart , Visual Basic.NET e Xojo . Em linguagens dinâmicas como Python , o conceito de um método de extensão é desnecessário porque as classes podem ser estendidas sem qualquer sintaxe especial (uma abordagem conhecida como "monkey-patching", empregada em bibliotecas como gevent ).

No VB.NET e no Oxygene, eles são reconhecidos pela presença da extensionpalavra-chave ou atributo " ". No Xojo, a Extendspalavra-chave " " é usada com métodos globais.

Em C #, eles são implementados como métodos estáticos em classes estáticas, com o primeiro argumento sendo de classe estendida e precedido pela thispalavra-chave " ".

Em Java, você adiciona métodos de extensão via Manifold , um arquivo jar que você adiciona ao classpath do seu projeto. Semelhante ao C #, um método de extensão Java é declarado estático em uma classe @Extension onde o primeiro argumento tem o mesmo tipo que a classe estendida e é anotado com @This.

Em Smalltalk, qualquer código pode adicionar um método a qualquer classe a qualquer momento, enviando uma mensagem de criação de método (como methodsFor:) para a classe que o usuário deseja estender. A categoria de método Smalltalk é convencionalmente nomeada após o pacote que fornece a extensão, cercada por asteriscos. Por exemplo, quando o código do aplicativo Etoys estende classes na biblioteca principal, os métodos adicionados são colocados na *etoys*categoria.

Em Ruby, como em Smalltalk, não há nenhum recurso de linguagem especial para extensão, pois Ruby permite que as classes sejam reabertas a qualquer momento com a classpalavra - chave, neste caso, para adicionar novos métodos. A comunidade Ruby geralmente descreve um método de extensão como uma espécie de patch monkey . Também existe um recurso mais recente para adicionar extensões seguras / locais aos objetos, chamado Refinamentos , mas é conhecido por ser menos usado.

Em Swift, a extensionpalavra - chave marca uma construção semelhante a uma classe que permite a adição de métodos, construtores e campos a uma classe existente, incluindo a capacidade de implementar uma nova interface / protocolo para a classe existente.

Métodos de extensão como recurso de habilitação

Além dos métodos de extensão que permitem que o código escrito por outros seja estendido conforme descrito abaixo, os métodos de extensão permitem padrões que são úteis por si próprios também. A razão predominante pela qual os métodos de extensão foram introduzidos foi a Consulta Integrada à Linguagem (LINQ). O suporte do compilador para métodos de extensão permite uma integração profunda do LINQ com o código antigo da mesma forma que com o novo código, bem como o suporte para a sintaxe de consulta que, por enquanto, é exclusiva das linguagens Microsoft .NET primárias .

Console.WriteLine(new[] { Math.PI, Math.E }.Where(d => d > 3).Select(d => Math.Sin(d / 2)).Sum());
// Output:
// 1

Centralize o comportamento comum

No entanto, os métodos de extensão permitem que os recursos sejam implementados uma vez de maneiras que permitem a reutilização sem a necessidade de herança ou sobrecarga de invocações de métodos virtuais , ou para exigir que os implementadores de uma interface implementem funcionalidades triviais ou terrivelmente complexas.

Um cenário particularmente útil é se o recurso opera em uma interface para a qual não há implementação concreta ou uma implementação útil não é fornecida pelo autor da biblioteca de classes, por exemplo, como é frequentemente o caso em bibliotecas que fornecem aos desenvolvedores uma arquitetura de plug-in ou funcionalidade semelhante .

Considere o código a seguir e suponha que seja o único código contido em uma biblioteca de classes. No entanto, cada implementador da interface ILogger ganhará a capacidade de escrever uma string formatada, apenas incluindo uma instrução using MyCoolLogger , sem ter que implementá-la uma vez e sem ser necessário criar uma subclasse de uma implementação de ILogger fornecida pela biblioteca de classes.

namespace MyCoolLogger {
    public interface ILogger { void Write(string text); }
    public static class LoggerExtensions {
        public static void Write(this ILogger logger, string format, params object[] args) { 
            if (logger != null)
                logger.Write(string.Format(format, args));
        }
    }
}
  • usar como:
    var logger = new MyLoggerImplementation();
    logger.Write("{0}: {1}", "kiddo sais", "Mam mam mam mam ...");
    logger.Write("{0}: {1}", "kiddo sais", "Ma ma ma ma... ");
    logger.Write("{0}: {1}", "kiddo sais", "Mama mama mama mama ");
    logger.Write("{0}: {1}", "kiddo sais", "Mamma mamma mamma ... ");
    logger.Write("{0}: {1}", "kiddo sais", "Elisabeth Lizzy Liz...");
    logger.Write("{0}: {1}", "mamma sais", "WHAT?!?!!!");
    logger.Write("{0}: {1}", "kiddo sais", "hi.");
    

Melhor acoplamento solto

Os métodos de extensão permitem que os usuários de bibliotecas de classes evitem declarar um argumento, variável ou qualquer outra coisa com um tipo que venha dessa biblioteca. A construção e a conversão dos tipos usados ​​na biblioteca de classes podem ser implementadas como métodos de extensão. Depois de implementar cuidadosamente as conversões e fábricas, alternar de uma biblioteca de classes para outra pode ser tão fácil quanto alterar a instrução using que torna os métodos de extensão disponíveis para o compilador vincular.

Interfaces de programador de aplicativos fluentes

Os métodos de extensão têm uso especial na implementação das chamadas interfaces fluentes. Um exemplo é a API de configuração do Entity Framework da Microsoft, que permite, por exemplo, escrever código que se assemelha ao inglês normal da forma mais prática possível.

Alguém poderia argumentar que isso também é possível sem métodos de extensão, mas descobrirá que, na prática, os métodos de extensão fornecem uma experiência superior porque menos restrições são colocadas na hierarquia de classes para fazê-la funcionar - e ler - conforme desejado.

O exemplo a seguir usa Entity Framework e configura a classe TodoList para ser armazenada na tabela de banco de dados Lists e define uma chave primária e uma chave estrangeira. O código deve ser entendido mais ou menos como: "Um TodoList tem a chave TodoListID, seu nome de conjunto de entidades é Lists e tem muitos TodoItem, cada um dos quais tem um TodoList obrigatório".

public class TodoItemContext : DbContext 
{
    public DbSet<TodoItem> TodoItems { get; set; }
    public DbSet<TodoList> TodoLists { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<TodoList>()
                    .HasKey(e => e.TodoListId)
                    .HasEntitySetName("Lists")
                    .HasMany(e => e.Todos)
                    .WithRequired(e => e.TodoList);
    }
}

Produtividade

Considere, por exemplo, IEnumerable e observe sua simplicidade - há apenas um método, embora seja mais ou menos a base do LINQ. Existem muitas implementações dessa interface no Microsoft .NET. No entanto, obviamente, teria sido oneroso exigir que cada uma dessas implementações implementasse toda a série de métodos que são definidos no namespace System.Linq para operar em IEnumerables, mesmo que a Microsoft tenha todo o código-fonte. Pior ainda, isso teria exigido que todos, exceto a Microsoft, considerassem usar o próprio IEnumerable para implementar todos esses métodos, o que teria sido muito anti-produtivo, visto o uso generalizado dessa interface tão comum. Em vez disso, implementando o único método dessa interface, LINQ pode ser usado mais ou menos imediatamente. Vendo especialmente na maioria dos casos, o método GetEnumerator de IEnumerable é delegado a uma coleção privada, lista ou implementação de GetEnumerator de array.

public class BankAccount : IEnumerable<decimal> 
{
    private List<Tuple<DateTime, decimal>> credits; // assumed all negative
    private List<Tuple<DateTime, decimal>> debits; // assumed all positive

    public IEnumerator<decimal> GetEnumerator() 
    {
        var query = from dc in debits.Union(credits) 
                    orderby dc.Item1 /* Date */ 
                    select dc.Item2; /* Amount */
    
        foreach (var amount in query)
            yield return amount;
    }
}
// given an instance of BankAccount called ba and a using System.Linq on top of the current file,
// one could now write ba.Sum() to get the account balance, ba.Reverse() to see most recent transactions first,
// ba.Average() to get the average amount per transaction, etcetera - without ever writing down an arithmetic operator

Desempenho

Dito isso, implementações adicionais de um recurso fornecido por um método de extensão podem ser adicionadas para melhorar o desempenho ou para lidar com implementações de interface implementadas de forma diferente, como fornecer ao compilador uma implementação de IEnumerable especificamente para matrizes (em System.SZArrayHelper), que irá escolher automaticamente para chamadas de método de extensão em referências digitadas por matriz, uma vez que seu argumento será mais específico (este valor T []) do que o método de extensão com o mesmo nome que opera em instâncias da interface IEnumerable (este valor IEnumerable).

Aliviando a necessidade de uma classe base comum

Com as classes genéricas, os métodos de extensão permitem a implementação do comportamento que está disponível para todas as instanciações do tipo genérico sem exigir que derivem de uma classe base comum e sem restringir os parâmetros de tipo a um ramo de herança específico. Esta é uma grande vitória, uma vez que as situações em que este argumento se mantém requerem uma classe base não genérica apenas para implementar o recurso compartilhado - que então requer que a subclasse genérica execute boxing e / ou casts sempre que o tipo usado for um dos argumentos de tipo .

Uso conservador

Uma nota deve ser colocada sobre a preferência dos métodos de extensão em relação a outros meios de alcançar a reutilização e o projeto orientado a objetos adequado. Os métodos de extensão podem 'confundir' os recursos de preenchimento automático dos editores de código, como o IntelliSense do Visual Studio, portanto, eles devem estar em seu próprio namespace para permitir que o desenvolvedor os importe seletivamente ou devem ser definidos em um tipo específico o suficiente para o método para aparecer no IntelliSense apenas quando realmente relevante e, considerando o acima, considere que eles podem ser difíceis de encontrar caso o desenvolvedor os espere, mas os perca do IntelliSense devido a uma instrução de uso ausente, uma vez que o desenvolvedor pode não ter associado o método com a classe que o define, ou mesmo com o namespace em que ele reside - mas sim com o tipo que ele estende e o namespace em que o tipo reside.

O problema

Na programação, surgem situações em que é necessário adicionar funcionalidade a uma classe existente - por exemplo, adicionando um novo método. Normalmente o programador modificaria o código-fonte da classe existente , mas isso força o programador a recompilar todos os binários com essas novas mudanças e requer que o programador seja capaz de modificar a classe, o que nem sempre é possível, por exemplo, ao usar classes de um terceiro - montagem da festa . Isso geralmente é contornado de uma das três maneiras, todas de alguma forma limitadas e não intuitivas:

  1. Herdar a classe e implementar a funcionalidade em um método de instância na classe derivada.
  2. Implemente a funcionalidade em um método estático adicionado a uma classe auxiliar.
  3. Use agregação em vez de herança .

Soluções C # atuais

A primeira opção é em princípio mais fácil, mas infelizmente é limitada pelo fato de que muitas classes restringem a herança de certos membros ou a proíbem completamente. Isso inclui a classe lacrada e os diferentes tipos de dados primitivos em C #, como int , float e string . A segunda opção, por outro lado, não compartilha dessas restrições, mas pode ser menos intuitiva, pois requer uma referência a uma classe separada em vez de usar os métodos da classe em questão diretamente.

Como exemplo, considere a necessidade de estender a classe string com um novo método reverso cujo valor de retorno é uma string com os caracteres em ordem reversa. Como a classe de string é um tipo selado, o método normalmente seria adicionado a uma nova classe de utilitário de maneira semelhante à seguinte:

string x = "some string value";
string y = Utility.Reverse(x);

Isso pode, no entanto, se tornar cada vez mais difícil de navegar à medida que a biblioteca de métodos e classes de utilitários aumenta, especialmente para os recém-chegados. A localização também é menos intuitiva porque, ao contrário da maioria dos métodos de string, não seria um membro da classe string, mas em uma classe completamente diferente. Uma sintaxe melhor seria, portanto, a seguinte:

string x = "some string value";
string y = x.Reverse();

Soluções VB.NET atuais

Em muitos aspectos, a solução VB.NET é semelhante à solução C # acima. No entanto, o VB.NET tem uma vantagem única de permitir que os membros sejam passados ​​para a extensão por referência (C # permite apenas por valor). Permitindo o seguinte;

Dim x As String = "some string value"
x.Reverse()

Como o Visual Basic permite que o objeto de origem seja passado por referência, é possível fazer alterações no objeto de origem diretamente, sem a necessidade de criar outra variável. Também é mais intuitivo, pois funciona de maneira consistente com os métodos de classes existentes.

Métodos de extensão

O novo recurso de linguagem dos métodos de extensão no C # 3.0, no entanto, torna o último código possível. Essa abordagem requer uma classe estática e um método estático, como segue.

public static class Utility
{
    public static string Reverse(this string input)
    {
        char[] chars = input.ToCharArray();
        Array.Reverse(chars);
        return new String(chars);
    }
}

Na definição, o modificador 'this' antes do primeiro argumento especifica que é um método de extensão (neste caso, para o tipo 'string'). Em uma chamada, o primeiro argumento não é 'passado' porque já é conhecido como o objeto de 'chamada' (o objeto antes do ponto).

A principal diferença entre chamar métodos de extensão e chamar métodos auxiliares estáticos é que os métodos estáticos são chamados em notação de prefixo , enquanto os métodos de extensão são chamados em notação de infixo . O último leva a um código mais legível quando o resultado de uma operação é usado para outra operação.

Com métodos estáticos
HelperClass.Operation2(HelperClass.Operation1(x, arg1), arg2)
Com métodos de extensão
x.Operation1(arg1).Operation2(arg2)

Conflitos de nomenclatura em métodos de extensão e métodos de instância

No C # 3.0, tanto um método de instância quanto um método de extensão com a mesma assinatura podem existir para uma classe. Nesse cenário, o método de instância é preferível ao método de extensão. Nem o compilador nem o Microsoft Visual Studio IDE avisam sobre o conflito de nomenclatura. Considere esta classe C #, onde o GetAlphabet()método é invocado em uma instância desta classe:

class AlphabetMaker 
{
    public void GetAlphabet()       
    {                               //When this method is implemented,
        Console.WriteLine("abc");   //it will shadow the implementation
    }                               //in the ExtensionMethods class.
}

static class ExtensionMethods
{
    public static void GetAlphabet(this AlphabetMaker am)   
    {                               //This will only be called                       
        Console.WriteLine("ABC");   //if there is no instance
    }                               //method with the same signature.   
}

Resultado de invocar GetAlphabet()em uma instância de AlphabetMakerse apenas o método de extensão existir:

ABC

Resultado se o método de instância e o método de extensão existirem:

abc

Veja também

Referências

links externos