Partilhar via


Métodos de extensão (Visual Basic)

Os métodos de extensão permitem que os desenvolvedores adicionem funcionalidade personalizada a tipos de dados já definidos sem criar um novo tipo derivado. Os métodos de extensão tornam possível escrever um método que pode ser chamado como se fosse um método de instância do tipo existente.

Observações

Um método de extensão só pode ser um Sub procedimento ou um Function procedimento. Não é possível definir uma propriedade, campo ou evento de extensão. Todos os métodos de extensão devem ser marcados com o System.Runtime.CompilerServices atributo <Extension> extension do namespace e devem ser definidos em um Module. Se um método de extensão é definido fora de um módulo, o compilador do Visual Basic gera erro BC36551, "Métodos de extensão podem ser definidos somente em módulos".

O primeiro parâmetro em uma definição de método de extensão especifica qual tipo de dados o método estende. Quando o método é executado, o primeiro parâmetro é vinculado à instância do tipo de dados que invoca o método.

O Extension atributo só pode ser aplicado a um Visual Basic Module, Subou Function. Se você aplicá-lo a um Class ou a , Structureo compilador do Visual Basic gerará BC36550 de erro, "O atributo 'Extension' pode ser aplicado somente às declarações 'Module', 'Sub' ou 'Function'".

Exemplo

O exemplo a seguir define uma Print extensão para o String tipo de dados. O método usa Console.WriteLine para exibir uma cadeia de caracteres. O parâmetro do Print método, aString, estabelece que o método estende a String classe.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()> 
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

End Module

Observe que a definição do método de extensão está marcada com o atributo <Extension()>extension . A marcação do módulo no qual o método é definido é opcional, mas cada método de extensão deve ser marcado. System.Runtime.CompilerServices deve ser importado para acessar o atributo extension.

Os métodos de extensão só podem ser declarados dentro de módulos. Normalmente, o módulo no qual um método de extensão é definido não é o mesmo módulo em que ele é chamado. Em vez disso, o módulo que contém o método de extensão é importado, se necessário, para colocá-lo no escopo. Depois que o módulo que contém Print estiver no escopo, o método pode ser chamado como se fosse um método de instância comum que não usa argumentos, como ToUpper:

Module Class1

    Sub Main()

        Dim example As String = "Hello"
        ' Call to extension method Print.
        example.Print()

        ' Call to instance method ToUpper.
        example.ToUpper()
        example.ToUpper.Print()

    End Sub

End Module

O próximo exemplo, PrintAndPunctuate, é também uma extensão para String, desta vez definido com dois parâmetros. O primeiro parâmetro, aString, estabelece que o método de extensão se estende String. O segundo parâmetro, punc, destina-se a ser uma cadeia de sinais de pontuação que é passada como um argumento quando o método é chamado. O método exibe a cadeia de caracteres seguida pelos sinais de pontuação.

<Extension()> 
Public Sub PrintAndPunctuate(ByVal aString As String, 
                             ByVal punc As String)
    Console.WriteLine(aString & punc)
End Sub

O método é chamado enviando um argumento string para punc: example.PrintAndPunctuate(".")

O exemplo a seguir mostra Print e PrintAndPunctuate definido e chamado. System.Runtime.CompilerServices é importado no módulo de definição para permitir o acesso ao atributo extension.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()>
    Public Sub Print(aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Sub PrintAndPunctuate(aString As String, punc As String)
        Console.WriteLine(aString & punc)
    End Sub
End Module

Em seguida, os métodos de extensão são trazidos para o escopo e chamados:

Imports ConsoleApplication2.StringExtensions

Module Module1

    Sub Main()
        Dim example As String = "Example string"
        example.Print()

        example = "Hello"
        example.PrintAndPunctuate(".")
        example.PrintAndPunctuate("!!!!")
    End Sub
End Module

Tudo o que é necessário para poder executar esses ou métodos de extensão semelhantes é que eles estejam no escopo. Se o módulo que contém um método de extensão estiver no escopo, ele será visível no IntelliSense e poderá ser chamado como se fosse um método de instância comum.

Observe que quando os métodos são invocados, nenhum argumento é enviado para o primeiro parâmetro. O parâmetro aString nas definições de método anteriores está vinculado a example, a instância de String que os chama. O compilador usará example como o argumento enviado para o primeiro parâmetro.

Se um método de extensão for chamado para um objeto definido como Nothing, o método de extensão será executado. Isso não se aplica a métodos de instância comuns. Você pode verificar Nothing explicitamente no método de extensão.

Tipos que podem ser estendidos

Você pode definir um método de extensão na maioria dos tipos que podem ser representados em uma lista de parâmetros do Visual Basic, incluindo o seguinte:

  • Classes (tipos de referência)
  • Estruturas (tipos de valor)
  • Interfaces
  • Delegados
  • Argumentos ByRef e ByVal
  • Parâmetros genéricos do método
  • Matrizes

Como o primeiro parâmetro especifica o tipo de dados que o método de extensão estende, ele é necessário e não pode ser opcional. Por essa razão, Optional parâmetros e ParamArray parâmetros não podem ser o primeiro parâmetro na lista de parâmetros.

Os métodos de extensão não são considerados na vinculação tardia. No exemplo a seguir, a instrução anObject.PrintMe() gera uma MissingMemberException exceção, a mesma exceção que você veria se a segunda PrintMe definição de método de extensão fosse excluída.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module4

    Sub Main()
        Dim aString As String = "Initial value for aString"
        aString.PrintMe()

        Dim anObject As Object = "Initial value for anObject"
        ' The following statement causes a run-time error when Option
        ' Strict is off, and a compiler error when Option Strict is on.
        'anObject.PrintMe()
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal str As String)
        Console.WriteLine(str)
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal obj As Object)
        Console.WriteLine(obj)
    End Sub

End Module

Melhores práticas

Os métodos de extensão fornecem uma maneira conveniente e poderosa de estender um tipo existente. No entanto, para usá-los com sucesso, há alguns pontos a considerar. Essas considerações se aplicam principalmente a autores de bibliotecas de classes, mas podem afetar qualquer aplicativo que use métodos de extensão.

Em geral, os métodos de extensão que você adiciona a tipos que você não possui são mais vulneráveis do que os métodos de extensão adicionados aos tipos que você controla. Uma série de coisas podem ocorrer em classes que você não possui que podem interferir com seus métodos de extensão.

  • Se existir algum membro de instância acessível que tenha uma assinatura compatível com os argumentos na instrução de chamada, sem a necessidade de estreitar conversões de argumento para parâmetro, o método de instância será usado em preferência a qualquer método de extensão. Portanto, se um método de instância apropriado for adicionado a uma classe em algum momento, um membro de extensão existente no qual você confia pode se tornar inacessível.

  • O autor de um método de extensão não pode impedir que outros programadores escrevam métodos de extensão conflitantes que podem ter precedência sobre a extensão original.

  • Você pode melhorar a robustez colocando métodos de extensão em seu próprio namespace. Os consumidores da sua biblioteca podem então incluir um namespace ou excluí-lo, ou selecionar entre namespaces, separadamente do resto da biblioteca.

  • Pode ser mais seguro estender interfaces do que estender classes, especialmente se você não possui a interface ou classe. Uma alteração em uma interface afeta todas as classes que a implementam. Portanto, o autor pode ser menos propenso a adicionar ou alterar métodos em uma interface. No entanto, se uma classe implementa duas interfaces que têm métodos de extensão com a mesma assinatura, nenhum método de extensão é visível.

  • Estenda o tipo mais específico possível. Em uma hierarquia de tipos, se você selecionar um tipo do qual muitos outros tipos são derivados, há camadas de possibilidades para a introdução de métodos de instância ou outros métodos de extensão que podem interferir com o seu.

Métodos de extensão, métodos de instância e propriedades

Quando um método de instância no escopo tem uma assinatura que é compatível com os argumentos de uma instrução de chamada, o método de instância é escolhido em preferência a qualquer método de extensão. O método de instância tem precedência mesmo se o método de extensão for uma correspondência melhor. No exemplo a seguir, ExampleClass contém um método de instância chamado ExampleMethod que tem um parâmetro do tipo Integer. O método ExampleMethod de extensão estende-se ExampleClasse tem um parâmetro do tipo Long.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Extension method")
End Sub

A primeira chamada para ExampleMethod no código a seguir chama o método extension, porque arg1 é Long e é compatível apenas com o Long parâmetro no método extension. A segunda chamada para ExampleMethod tem um Integer argumento, arg2e chama o método de instância.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the extension method.
    example.exampleMethod(arg1)
    ' The following statement calls the instance method.
    example.exampleMethod(arg2)
End Sub

Agora inverta os tipos de dados dos parâmetros nos dois métodos:

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Long)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Integer)
    Console.WriteLine("Extension method")
End Sub

Desta vez, o código em Main chama o método de instância ambas as vezes. Isso ocorre porque ambos arg1 têm arg2 uma conversão de ampliação para Long, e o método de instância tem precedência sobre o método de extensão em ambos os casos.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the instance method.
    example.ExampleMethod(arg1)
    ' The following statement calls the instance method.
    example.ExampleMethod(arg2)
End Sub

Portanto, um método de extensão não pode substituir um método de instância existente. No entanto, quando um método de extensão tem o mesmo nome que um método de instância, mas as assinaturas não entram em conflito, ambos os métodos podem ser acessados. Por exemplo, se a classe ExampleClass contém um método nomeado ExampleMethod que não usa argumentos, métodos de extensão com o mesmo nome, mas assinaturas diferentes são permitidos, conforme mostrado no código a seguir.

Imports System.Runtime.CompilerServices

Module Module3

    Sub Main()
        Dim ex As New ExampleClass
        ' The following statement calls the extension method.
        ex.ExampleMethod("Extension method")
        ' The following statement calls the instance method.
        ex.ExampleMethod()
    End Sub

    Class ExampleClass
        ' Define an instance method named ExampleMethod.
        Public Sub ExampleMethod()
            Console.WriteLine("Instance method")
        End Sub
    End Class

    <Extension()> 
    Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal stringParameter As String)
        Console.WriteLine(stringParameter)
    End Sub

End Module

A saída deste código é a seguinte:

Extension method
Instance method

A situação é mais simples com propriedades: se um método de extensão tem o mesmo nome que uma propriedade da classe que ele estende, o método de extensão não é visível e não pode ser acessado.

Precedência do método de extensão

Quando dois métodos de extensão que têm assinaturas idênticas estão no escopo e acessíveis, aquele com maior precedência será invocado. A precedência de um método de extensão baseia-se no mecanismo utilizado para introduzir o método no âmbito de aplicação. A lista a seguir mostra a hierarquia de precedência, da mais alta para a mais baixa.

  1. Métodos de extensão definidos dentro do módulo atual.

  2. Métodos de extensão definidos dentro de tipos de dados no namespace atual ou em qualquer um de seus pais, com namespaces filho tendo maior precedência do que namespaces pai.

  3. Métodos de extensão definidos dentro de qualquer tipo importa no arquivo atual.

  4. Métodos de extensão definidos dentro de qualquer importação de namespace no arquivo atual.

  5. Métodos de extensão definidos dentro de qualquer importação de tipo de nível de projeto.

  6. Métodos de extensão definidos dentro de qualquer importação de namespace no nível do projeto.

Se a precedência não resolver a ambiguidade, você pode usar o nome totalmente qualificado para especificar o método que você está chamando. Se o Print método no exemplo anterior for definido em um módulo chamado StringExtensions, o nome totalmente qualificado será StringExtensions.Print(example) em vez de example.Print().

Consulte também