Los métodos
de extensión permiten a los desarrolladores agregar funcionalidad personalizada
a los tipos de datos ya definidos sin 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.
Un método de
extensión puede ser únicamente un procedimiento Sub o un procedimiento
Function.No se puede definir ninguna propiedad, campo o evento de
extensión.Todos los métodos de extensión se deben marcar con el atributo de
extensión, <Extension()>, del espacio de nombres
System.Runtime.CompilerServices.
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.
Ejemplo
Descripción
En el
siguiente ejemplo 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 de extensión extiende la clase String.
VB
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 de método de extensión se marca con el atributo de extensión
<Extension()>.Marcar el módulo en el que se define el método es opcional,
pero se debe marcar cada método de extensión. System.Runtime.CompilerServices
debe importarse para poder obtener acceso al atributo de extensión.
Los métodos
de extensión se pueden declarar únicamente dentro de los módulos.Normalmente,
el módulo en el que se define un método de extensión no es el mismo que el
módulo en el que se llama.En su lugar, se importa el módulo que contiene el
método de extensión, si fuera necesario, para incluirlo en el ámbito.Después de
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 ordinario que no toma argumentos, como
ToUpper:
VB
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
En el
ejemplo siguiente, PrintAndPunctuate, también es una extensión de String, esta
vez definida 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 los signos de
puntuación.
VB
<Extension()>
Public Sub
PrintAndPunctuate(ByVal aString As String,
ByVal punc As
String)
Console.WriteLine(aString & punc)
End Sub
Se llama al
método enviando un argumento de cadena para punc:
example.PrintAndPunctuate(".")
En el
siguiente ejemplo, se muestra cómo se definen y se invocan Print y
PrintAndPunctuate. System.Runtime.CompilerServices se importa en el módulo de
definición para permitir el acceso al atributo de extensión.
Código
VB
Imports
System.Runtime.CompilerServices
Module StringExtensions
<Extension()>
Public Sub Print(ByVal aString As String)
Console.WriteLine(aString)
End Sub
<Extension()>
Public Sub PrintAndPunctuate(ByVal aString
As String,
ByVal punc As
String)
Console.WriteLine(aString & punc)
End Sub
End Module
Después, los
métodos de extensión se incluyen en el ámbito y se llaman.
VB
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
Comentarios
Todo esto es
necesario para poder ejecutar estos métodos o métodos de extensión similares
que están en el ámbito.Si el módulo que contiene un método de extensión está en
el ámbito, está visible en IntelliSense y se le llama 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 en las definiciones de método anteriores, se
enlaza a example, la instancia de String que los llama.El compilador usará
example como el argumento que se envía al primer parámetro.
Si un método
de extensión se llama para un objeto que está establecido en Nothing, el método
de extensión se ejecuta.Esto no se aplica a los métodos normales de la
instancia.Puede comprobar explícitamente si hay Nothing 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érico
Matrices
Puesto que
el primer parámetro especifica el tipo de datos que el método de extensión
extiende, es obligatorio y no puede ser opcional.Por esa razón, los parámetros
Optional y ParamArray no pueden ser el primer parámetro de la lista de
parámetros.
Los métodos
de extensión no se consideran en los enlaces en tiempo de ejecución.En el
siguiente ejemplo, la instrucción anObject.PrintMe() genera una excepción
MissingMemberException, la misma excepción que se produciría si se eliminara la
segunda definición del método de extensión PrintMe.
VB
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 conveniente y eficaz de extender un tipo
existente.Sin embargo, para usarlos correctamente se deben tener en cuenta
algunos puntos.Estas consideraciones se aplican principalmente a los autores de
bibliotecas de clases, pero podrían afectar a cualquier aplicación que use
métodos de extensión.
Más en
general, los métodos de extensión que agrega a los tipos de los que no es
propietario son más vulnerables que los métodos de extensión agregados a los
tipos que controla.Se pueden producir algunos conflictos en las clases de las
que no es propietario que pueden interferir con sus métodos de extensión.
Si existe
cualquier miembro de instancia accesible con una firma compatible con los
argumentos en la instrucción de llamada, sin requerir conversiones de
restricción del argumento al parámetro, el método de instancia se usará antes
que cualquier método de extensión.Por consiguiente, si se agrega un método de
instancia adecuado a una clase en un momento dado, puede que un miembro de
extensión existente en el que confía se vuelva inaccesible.
El autor de
un método de extensión no puede evitar que otros programadores escriban métodos
de extensión conflictivos que pueden tener prioridad sobre la extensión
original.
Puede
mejorar la solidez colocando los métodos de extensión en su propio espacio de
nombres.Entonces, los usuarios de su biblioteca pueden incluir un espacio de
nombres o excluirlo, o bien seleccionar entre los espacios de nombres, por
separado en el resto de la biblioteca.
Puede ser
más seguro extender interfaces que extender clases, sobre todo si no posee la
interfaz o la clase.Un cambio en una interfaz afecta a cada clase que la
implementa.Por consiguiente, es menos probable que el autor pueda agregar o
cambiar métodos en una interfaz.Sin embargo, 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 está 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 niveles de posibilidades para la
inclusió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
Cuando un
método de instancia dentro del ámbito tiene una firma que es compatible con los
argumentos de una instrucción de llamada, se elige el método de instancia de
preferencia a cualquier método de extensión.El método de instancia tiene la
prioridad aun cuando el método de extensión tenga una mejor coincidencia.En el
ejemplo siguiente, ExampleClass contiene un método de instancia denominado
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.
VB
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 sólo es compatible con el parámetro Long en el método de
extensión.La segunda llamada a ExampleMethod tiene un argumento Integer, arg2,
y llama al método de instancia.
VB
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 en los dos métodos:
VB
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
Ahora el
código de Main llama al método de instancia las dos veces.Esto se debe a que
arg1 y 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.
VB
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.Sin embargo, cuando 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 tener
acceso a ambos métodos.Por ejemplo, si la clase ExampleClass contiene un método
denominado ExampleMethod que no toma ningún argumento, se permiten métodos de
extensión con el mismo nombre pero con firmas diferentes, como se muestra en el
código siguiente.
VB
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
Los
resultados de este código son los siguientes:
Extension
method
Instance
method
La situación
es más fácil 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 está
visible y no se puede tener acceso a él.
Prioridad
del método de extensión
Cuando dos
métodos de extensión con firmas idénticas están en el ámbito y se encuentran
accesibles, se invoca al de mayor prioridad.La prioridad de un método de
extensión se basa en el mecanismo que se usa para incluir al método en el
ámbito.La lista siguiente 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 los tipos de datos en el espacio de nombres
actual o cualquiera de sus elementos primarios. 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 importaciones en el archivo
actual.
Métodos de
extensión definidos dentro de cualquier importación de espacio de nombres en el
archivo actual.
Métodos de
extensión definidos dentro de cualquier tipo de importaciones de nivel de
proyecto.
Métodos de
extensión definidos dentro de cualquier importación de espacio de nombres de
nivel de proyecto.