Métodos de extensão (Visual Basic)
Os métodos de extensão permitem que os desenvolvedores adicionem funcionalidade personalizada aos tipos de dados que já estão definidos sem criar um tipo derivado. Os métodos de extensão possibilitam a gravação de um método que pode ser chamado como se fosse um método de instância do tipo existente.
Comentários
Um método de extensão pode ser apenas um procedimento Sub
ou um procedimento Function
. Você não pode definir uma propriedade um campo ou um evento de extensão. Todos os métodos de extensão precisam ser marcados com o atributo de extensão <Extension>
do namespace System.Runtime.CompilerServices e precisam ser definidos em um Módulo. Se um método de extensão for definido fora de um módulo, o compilador do Visual Basic gerará o erro BC36551, "Métodos de extensão só podem ser definidos 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 é associado à instância do tipo de dados que invoca o método.
O atributo Extension
só pode ser aplicado a um Module
, Sub
ou Function
do Visual Basic. Se você o aplicar a um Class
ou um Structure
, o compilador do Visual Basic gerará o erro BC36550, "O atributo 'Extension' só pode ser aplicado às declarações 'Module', 'Sub' ou 'Function'".
Exemplo
O exemplo a seguir define uma extensão Print
para o tipo de dados String. O método usa Console.WriteLine
para exibir uma cadeia de caracteres. O parâmetro do método Print
, aString
, estabelece que o método estende a classe String.
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 de extensão <Extension()>
. Marcar o módulo no qual o método é definido é opcional, mas cada método de extensão precisa ser marcado. System.Runtime.CompilerServices precisa ser importado para acessar o atributo de extensão.
Os métodos de extensão podem ser declarados apenas em módulos. Normalmente, o módulo no qual um método de extensão é definido não é o mesmo módulo do qual 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 definida 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 caracteres de marcas de pontuação que é passada como um argumento quando o método é chamado. O método exibe a cadeia de caracteres seguida pelas marcas 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 de cadeia de caracteres para punc
: example.PrintAndPunctuate(".")
O exemplo a seguir mostra Print
e PrintAndPunctuate
definidos e chamados. System.Runtime.CompilerServices é importado no módulo de definição para habilitar o acesso ao atributo de extensão.
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 colocados no 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 ser capaz de 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 estará 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á associado à example
, a instância de String
que as 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 aos 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 de método genérico
- 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 esse motivo, parâmetros Optional
e parâmetros ParamArray
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 associação tardia. No exemplo a seguir, a instrução anObject.PrintMe()
gera uma exceção MissingMemberException, a mesma exceção que você veria se a segunda definição do método de extensão PrintMe
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
Práticas recomendadas
Os métodos de extensão fornecem uma forma conveniente e poderosa de estender um tipo existente. No entanto, para usá-los com êxito, há alguns pontos a serem considerados. Essas considerações se aplicam principalmente a autores de bibliotecas de classes, mas podem afetar qualquer aplicativo que use métodos de extensão.
Geralmente, os métodos de extensão que você adiciona aos tipos que você não tem são mais vulneráveis do que os métodos de extensão adicionados aos tipos que você controla. Várias coisas podem ocorrer em classes que você não tem que podem interferir com seus métodos de extensão.
Se houver um membro de instância acessível que tenha uma assinatura compatível com os argumentos na instrução de chamada, sem conversões de restrição necessárias de argumento para parâmetro, o método de instância será usado em preferência para 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ê depende poderá se tornar inacessível.
O autor de um método de extensão não pode impedir que outros programadores gravem métodos de extensão conflitantes que possam ter precedência sobre a extensão original.
Você pode melhorar a robustez colocando métodos de extensão no próprio namespace deles. Os consumidores da biblioteca podem incluir um namespace ou excluí-lo ou selecionar entre namespaces, separadamente do restante da biblioteca.
Pode ser mais seguro estender interfaces do que estender classes, especialmente se você não tem a interface ou a 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 implementar duas interfaces que têm métodos de extensão com a mesma assinatura, nenhum método de extensão ficará visível.
Estenda o tipo mais específico que puder. 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 no 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 compatível com os argumentos de uma instrução de chamada, o método de instância é escolhido em preferência para qualquer método de extensão. O método de instância tem precedência mesmo que o método de extensão seja uma correspondência melhor. No exemplo a seguir, ExampleClass
contém um método de instância chamado ExampleMethod
que tem um parâmetro de tipo Integer
. O método de extensão ExampleMethod
estende ExampleClass
e tem um parâmetro de 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 o código a ExampleMethod
seguir chama o método de extensão, pois arg1
é Long
e é compatível apenas com o parâmetro Long
no método de extensão. A segunda chamada para ExampleMethod
tem um argumento Integer
, arg2
, e 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
e arg2
têm uma conversão de expansã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 de 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
contiver um método nomeado ExampleMethod
que não usa argumentos, os 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 desse código é a seguinte:
Extension method
Instance method
A situação é mais simples com propriedades: se um método de extensão tiver o mesmo nome que uma propriedade da classe que ele estende, o método de extensão não estará visível e não poderá ser acessado.
Precedência do método de extensão
Quando dois métodos de extensão que têm assinaturas idênticas estiverem no escopo e acessíveis, aquele com precedência mais alta será invocado. A precedência de um método de extensão baseia-se no mecanismo usado para colocar o método no escopo. A lista a seguir mostra a precedência da hierarquia, da mais alta para a mais baixa.
Métodos de extensão definidos dentro do módulo atual.
Métodos de extensão definidos dentro de tipos de dados no namespace atual ou em qualquer um dos pais dele, com namespaces filho com precedência maior do que namespaces pai.
Métodos de extensão definidos dentro de qualquer tipo de importação no arquivo atual.
Métodos de extensão definidos dentro de qualquer namespace no arquivo atual.
Métodos de extensão definidos dentro de qualquer importação de tipo no nível do projeto.
Métodos de extensão definidos dentro de qualquer importação de namespace do projeto.
Se a precedência não resolver a ambiguidade, você poderá usar o nome totalmente qualificado para especificar o método que você está chamando. Se o método Print
no exemplo anterior for definido em um módulo nomeado StringExtensions
, o nome totalmente qualificado será StringExtensions.Print(example)
em vez de example.Print()
.