Compartir a través de


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 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.

Observaciones

Un método de extensión solo puede ser un Sub procedimiento o un Function procedimiento. No se puede definir una propiedad de extensión, un campo o un evento. 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 se define un método de extensión fuera de un módulo, el compilador de Visual Basic genera un 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 el método .

El Extension atributo solo se puede aplicar a visual Basic Module, Subo Function. Si lo aplicas a un Class o a un Structure, el compilador de Visual Basic genera el error BC36550: el atributo "Extensión" solo se puede aplicar a las declaraciones "Module", "Sub" o "Function".

Ejemplo

En el ejemplo siguiente se define una Print extensión para el tipo de String datos. El método usa Console.WriteLine para mostrar una cadena. El parámetro del Print método , aStringestablece que el método extiende la String clase .

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, pero cada método de extensión debe marcarse. System.Runtime.CompilerServices debe importarse para tener acceso al atributo de extensión.

Los métodos de extensión solo se pueden declarar dentro de los 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 invoca. En su lugar, se importa el módulo que contiene el método de extensión, si es 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 normal que no requiere 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

El ejemplo siguiente, PrintAndPunctuate, también es una extensión a 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 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(".")

El siguiente ejemplo muestra 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 usará example como argumento enviado al primer parámetro.

Si se llama a un método de extensión para un objeto que se establece 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érico
  • 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, ni Optional ni ParamArray 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 MissingMemberException excepción, la misma excepción que vería si se eliminara la segunda PrintMe definición del método de extensión.

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. Sin embargo, 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, el método de instancia se usará en preferencia para 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 del que dependes puede dejar de estar accesible.

  • El autor de un método de extensión no puede impedir 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 ampliar las interfaces que extender las clases, especialmente si no tiene propiedad sobre la interfaz o la 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. 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 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 introducció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 coincide con los argumentos de una declaración de llamada, se prefiere el método de instancia a cualquier método de extensión. El método de instancia tiene prioridad incluso si el método de extensión es más adecuado. En el ejemplo siguiente, ExampleClass contiene un método de instancia denominado ExampleMethod que tiene un parámetro de tipo Integer. El método ExampleMethod de extensión extiende ExampleClassy 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, ya que arg1 es Long y solo es compatible con el Long parámetro en el método de extensión. La segunda llamada a ExampleMethod tiene un Integer argumento, arg2y 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 invierte los tipos de datos de los parámetros en 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 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 argumentos, se permiten métodos de extensión con el mismo nombre, pero se permiten 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

La salida de este código es la 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 tener acceso a él.

Precedencia del método de extensión

Cuando dos métodos de extensión que tienen firmas idénticas están en el ámbito y son accesibles, se invocará el que tenga mayor prioridad. La precedencia de un método de extensión se basa en el mecanismo usado para poner el método en el ámbito. En la lista siguiente se muestra la jerarquía de precedencia, de la más alta a la más baja.

  1. Métodos de extensión definidos dentro del módulo actual.

  2. 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.

  3. Métodos de extensión definidos dentro de cualquier tipo de importación del archivo actual.

  4. Métodos de extensión definidos dentro de cualquier namespace importado en el archivo actual.

  5. Métodos de extensión definidos en cualquier importación de tipos a nivel de proyecto.

  6. Métodos de extensión definidos dentro de cualquier importación de un espacio de nombres a 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 Print método del ejemplo anterior se define en un módulo denominado StringExtensions, el nombre completo es StringExtensions.Print(example) en lugar de example.Print().

Consulte también