Métodos de extensión (Visual Basic)
Los métodos de extensión permiten a los desarrolladores agregar funcionalidad personalizada a los tipos de datos que ya están definidos sin necesidad de crear un nuevo tipo derivado. Los métodos de extensión permiten escribir un método al que se puede llamar como si fuera un método de instancia del tipo existente.
Comentarios
Un método de extensión solo puede ser un procedimiento Sub
o un procedimiento Function
. No se puede definir una propiedad, un campo o un evento de extensión. Todos los métodos de extensión deben marcarse con el atributo de extensión <Extension>
del espacio de nombres System.Runtime.CompilerServices y deben definirse en un módulo. Si un método de extensión se define fuera de un módulo, el compilador de Visual Basic genera el error BC36551, "Los métodos de extensión solo se pueden definir en módulos".
El primer parámetro de una definición de método de extensión especifica qué tipo de datos extiende el método. Cuando se ejecuta el método, el primer parámetro se enlaza a la instancia del tipo de datos que invoca al método.
El atributo Extension
solo se puede aplicar a un elemento Module
, Sub
o Function
de Visual Basic. Si se aplica a Class
o Structure
, el compilador de Visual Basic genera el error BC36550, "El atributo 'Extension' solo se puede aplicar a declaraciones 'Module', 'Sub' o 'Function'".
Ejemplo
En el ejemplo siguiente se define una extensión Print
para el tipo de datos String. El método usa Console.WriteLine
para mostrar una cadena. El parámetro del método Print
, aString
, establece que el método extiende la clase String.
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension()>
Public Sub Print(ByVal aString As String)
Console.WriteLine(aString)
End Sub
End Module
Observe que la definición del método de extensión está marcada con el atributo de extensión <Extension()>
. Marcar el módulo en el que se define el método es opcional, aunque debe marcarse cada método de extensión. System.Runtime.CompilerServices debe importarse para acceder al atributo de extensión.
Los métodos de extensión solo se pueden declarar en módulos. Normalmente, el módulo en el que se define un método de extensión no es el mismo módulo en el que se llama. En su lugar, el módulo que contiene el método de extensión se importa, si fuera necesario, para incluirlo en el ámbito. Una vez que el módulo que contiene Print
está en el ámbito, se puede llamar al método como si fuera un método de instancia normal que no toma ningún argumento, 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
El ejemplo siguiente, PrintAndPunctuate
, también es una extensión de String, esta vez definido con dos parámetros. El primer parámetro, aString
, establece que el método de extensión extiende String. El segundo parámetro, punc
, está pensado para ser una cadena de signos de puntuación que se pasa como argumento cuando se llama al método. El método muestra la cadena seguida de las marcas de puntuación.
<Extension()>
Public Sub PrintAndPunctuate(ByVal aString As String,
ByVal punc As String)
Console.WriteLine(aString & punc)
End Sub
Se llama al método mediante el envío de un argumento de cadena para punc
: example.PrintAndPunctuate(".")
En el ejemplo siguiente se muestran Print
y PrintAndPunctuate
definidos y llamados. System.Runtime.CompilerServices se importa en el módulo de definición para habilitar el acceso al atributo de extensión.
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
A continuación, los métodos de extensión se incluyen en el ámbito y se llaman:
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
Todo lo necesario para poder ejecutar estos métodos de extensión o similares es que estén en el ámbito. Si el módulo que contiene un método de extensión está en el ámbito, es visible en IntelliSense y se puede llamar como si fuera un método de instancia normal.
Observe que cuando se invocan los métodos, no se envía ningún argumento para el primer parámetro. El parámetro aString
de las definiciones de método anteriores está enlazado a example
, la instancia de String
que las llama. El compilador usa example
como argumento enviado al primer parámetro.
Si se llama a un método de extensión de un objeto que está establecido en Nothing
, el método de extensión se ejecuta. Esto no se aplica a los métodos de instancia normales. Puede comprobar Nothing
explícitamente en el método de extensión.
Tipos que se pueden extender
Puede definir un método de extensión en la mayoría de los tipos que se pueden representar en una lista de parámetros de Visual Basic, incluidos los siguientes:
- Clases (tipos de referencia)
- Estructuras (tipos de valor)
- Interfaces
- Delegados
- Argumentos ByRef y ByVal
- Parámetros de método genéricos
- Matrices
Dado que el primer parámetro especifica el tipo de datos que extiende el método de extensión, es necesario y no puede ser opcional. Por ese motivo, los parámetros Optional
y los parámetros ParamArray
no pueden ser el primer parámetro de la lista de parámetros.
Los métodos de extensión no se tienen en cuenta en el enlace tardío. En el ejemplo siguiente, la instrucción anObject.PrintMe()
genera una excepción MissingMemberException, la misma excepción que se vería si se eliminara la segunda definición del método de extensión PrintMe
.
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
Procedimientos recomendados
Los métodos de extensión proporcionan una manera cómoda y eficaz de extender un tipo existente. Pero para usarlos correctamente, hay algunos puntos que se deben tener en cuenta. Estas consideraciones se aplican principalmente a los autores de bibliotecas de clases, pero pueden afectar a cualquier aplicación que use métodos de extensión.
Por lo general, los métodos de extensión que se agregan a tipos que no se poseen son más vulnerables que los métodos de extensión agregados a tipos que se controlan. Pueden ocurrir varias cosas en las clases que no se poseen que pueden interferir con los métodos de extensión.
Si existe algún miembro de instancia accesible que tenga una firma compatible con los argumentos de la instrucción de llamada, sin conversiones de restricción necesarias del argumento al parámetro, se prefiere usar el método de instancia a cualquier método de extensión. Por lo tanto, si se agrega un método de instancia adecuado a una clase en algún momento, un miembro de extensión existente en el que se base puede volverse inaccesible.
El autor de un método de extensión no puede evitar que otros programadores escriban métodos de extensión en conflicto que puedan tener prioridad sobre la extensión original.
Puede mejorar la solidez si coloca métodos de extensión en su propio espacio de nombres. Luego, los consumidores de la biblioteca pueden incluir un espacio de nombres o excluirlo, o seleccionar entre espacios de nombres, independientemente del resto de la biblioteca.
Puede ser más seguro extender interfaces que extender clases, especialmente si no se posee la interfaz o clase. Un cambio en una interfaz afecta a cada clase que la implementa. Por lo tanto, es menos probable que el autor agregue o cambie métodos en una interfaz. Pero si una clase implementa dos interfaces que tienen métodos de extensión con la misma firma, ninguno de los métodos de extensión es visible.
Extienda el tipo más específico que pueda. En una jerarquía de tipos, si selecciona un tipo del que se derivan muchos otros tipos, hay capas de posibilidades para la incorporación de métodos de instancia u otros métodos de extensión que podrían interferir con el suyo.
Métodos de extensión, métodos de instancia y propiedades
Si un método de instancia del ámbito tiene una firma que es compatible con los argumentos de una instrucción de llamada, se prefiere el método de instancia a cualquier método de extensión. El método de instancia tiene prioridad aunque el método de extensión sea una mejor coincidencia. En el ejemplo siguiente, ExampleClass
contiene un método de instancia de nombre ExampleMethod
que tiene un parámetro de tipo Integer
. El método de extensión ExampleMethod
extiende ExampleClass
y tiene un 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
La primera llamada a ExampleMethod
en el código siguiente llama al método de extensión, porque arg1
es Long
y solo es compatible con el parámetro Long
del método de extensión. La segunda llamada a ExampleMethod
tiene un argumento Integer
, arg2
, y llama al método de instancia.
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
Ahora invierta los tipos de datos de los parámetros de los dos 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
Esta vez, el código de Main
llama al método de instancia ambas veces. Esto se debe a que tanto arg1
como arg2
tienen una conversión de ampliación a Long
, y el método de instancia tiene prioridad sobre el método de extensión en ambos 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
Por lo tanto, un método de extensión no puede reemplazar a un método de instancia existente. Pero si un método de extensión tiene el mismo nombre que un método de instancia, pero las firmas no entran en conflicto, se puede acceder a ambos métodos. Por ejemplo, si la clase ExampleClass
contiene un método de nombre ExampleMethod
que no toma argumentos, se permiten métodos de extensión con el mismo nombre pero firmas diferentes, como se muestra en el código siguiente.
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
El resultado de este código es el siguiente:
Extension method
Instance method
La situación es más sencilla con propiedades: si un método de extensión tiene el mismo nombre que una propiedad de la clase que extiende, el método de extensión no es visible y no se puede acceder a él.
Prioridad del método de extensión
Si dos métodos de extensión que tienen firmas idénticas están en el ámbito y son accesibles, se invoca al de mayor prioridad. La prioridad de un método de extensión se basa en el mecanismo empleado para incluirlo en el ámbito. En la siguiente lista se muestra la jerarquía de prioridad, de mayor a menor.
Métodos de extensión definidos dentro del módulo actual.
Métodos de extensión definidos dentro de tipos de datos del espacio de nombres actual o cualquiera de sus elementos primarios, donde los espacios de nombres secundarios tienen mayor prioridad que los espacios de nombres primarios.
Métodos de extensión definidos dentro de cualquier tipo de importación del archivo actual.
Métodos de extensión definidos dentro de cualquier importación de espacio de nombres del archivo actual.
Métodos de extensión definidos dentro de cualquier importación de tipo de nivel de proyecto.
Métodos de extensión definidos dentro de cualquier importación de espacio de nombres de nivel de proyecto.
Si la prioridad no resuelve la ambigüedad, puede usar el nombre completo para especificar el método al que llama. Si el método Print
del ejemplo anterior se define en un módulo de nombre StringExtensions
, el nombre completo es StringExtensions.Print(example)
en lugar de example.Print()
.