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.

Los métodos de extensión más comunes son los operadores de consulta estándar de LINQ que agregan funcionalidad de consulta a los tipos System.Collections.IEnumerable y System.Collections.Generic.IEnumerable(T) existentes. Para usar los operadores de consulta estándar, primero inclúyalos en el ámbito con una directiva using System.Linq.Después, cualquier tipo que implemente IEnumerable(T) parecerá tener métodos de instancia como GroupBy, OrderBy, Average, etc.Puede ver estos métodos adicionales con la característica de finalización de instrucciones IntelliSense al escribir un “punto” después de una instancia de un tipo IEnumerable(T) como List(T) o Array.

En el ejemplo siguiente se muestra cómo llamar al método de operador de consulta estándar OrderBy en un arreglo de enteros. La expresión entre paréntesis es una expresión lambda. Muchos operadores de consulta estándar usan expresiones lambda como parámetros, pero no es obligatorio para los métodos de extensión.
Para más información sobre expresiones lambda, vea el artículo Expresiones Lambda

Imports System.Collections.Generic
Imports System.Linq
Imports System.Text

Namespace EjemploOrderBy
    Module Program
        Sub Main(args As String())
            Dim ints As Integer() = {10, 45, 15, 39, 21, 26}
            Dim result = ints.OrderBy(Function(g) g)
            For Each i As Integer In result
                System.Console.Write(Convert.ToString(i) & " ")
            Next
            System.Console.WriteLine(vbLf & "Pulse cualquier " & 
                                     "tecla para terminar...")
            System.Console.ReadKey(True)
        End Sub
    End Module
End Namespace

Como su nombre lo indica, un método de extensión puede ser únicamente un método (un procedimiento Sub o un procedimiento Function); es decir, 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 la 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

En el siguiente ejemplo se define un método de extensión Print para 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 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():

Module Class1
    Sub Main()
        Dim ejemplo As String = "Hola a todos."
        ' Llama al método de extensión Print.
        ejemplo.Print()

        ' Llama al método de instancia ToUpper, 
        ' y luego al método de extensión Print .
         ejemplo.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, unString, 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:

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

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:

Imports System.Runtime.CompilerServices

Module StringExtensions
    <Extension()> 
    Public Sub Print(ByVal unString As String)
        Console.WriteLine(unString)
    End Sub

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

Después, los métodos de extensión se incluyen en el ámbito y se llaman.

Imports ConsoleApplication2.StringExtensions
Module Module1
    Sub Main()
        Dim ejemplo As String = "Ejemplo"
        ejemplo.Print()

        ejemplo= "Hola"
        ejemplo.PrintAndPunctuate(".")
        ejemplo.PrintAndPunctuate("!!!!")
    End Sub
End Module

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, se hace 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 ejemplo, la instancia de String que los llama. El compilador usará ejemplo como el argumento que se envía al primer parámetro.

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:

  1. Clases (tipos de referencia)
  2. Estructuras (tipos de valor)
  3. Interfaces
  4. Delegados
  5. Argumentos ByRef y ByVal
  6. Parámetros de métodos genéricos
  7. Arrays

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.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module1
    Sub Main()
        Dim unString As String = "Valor inicial para unString"
        unString.PrintMe()

        Dim unObject As Object = "Valor inicial para unObject"
        ' La siguiente sentencia causa un error en tiempo de ejecución
        ' cuando Option Strict es off, y un error de compilación
        ' cuando Option Strict es on.
        'unObject.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
Conversiones de ampliación y de restricción: Un concepto que se debe tener claramente es que cuando se convierten tipos, estas conversiones pueden ser de dos tipos:
  1. Conversión de ampliación: cambia un valor a un tipo de datos que puede alojar cualquier valor posible de los datos originales.
  2. Conversión de restricción: cambia un valor a un tipo de datos que quizás no pueda contener alguno de los valores posibles.
Option Strict restringe las conversiones implícitas de tipos de datos sólo a las conversiones de ampliación. Si se utiliza, la instrucción Option Strict debe aparecer en un archivo antes de cualquier otra instrucción de código fuente. Visual Basic permite realizar conversiones de muchos tipos de datos a otros tipos de datos. Sin embargo, se pueden perder datos cuando el valor de un tipo de datos se convierte en un tipo de datos con menor precisión o menor capacidad (conversiones de restricción). Se produce un error en tiempo de ejecución si falla la conversión de restricción. Option Strict garantiza la notificación en tiempo de compilación de estas conversiones de restricción para que puedan evitarse. Además de no permitir las conversiones de restricción implícitas, Option Strict genera un error para el enlace en tiempo de ejecución (late binding). Un objeto es de enlace en tiempo de ejecución cuando se asigna a una variable que está declarada como del tipo Object. Dado que Option Strict On proporciona definición inflexible de tipos, evita las conversiones de tipos no deseadas con pérdida de datos, deniega el enlace en tiempo de ejecución y mejora el rendimiento, es muy recomendable utilizarla.

Buenas Prácticas en el uso de los métodos de Extensión

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:

Class ExampleClass
    ' Define un método de instancia llamado ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Método de instancia")
    End Sub
End Class

 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Método de Extensión")
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.

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

    ' La siguiente sentencia llama al método de extensión.
    example.exampleMethod(arg1)

    ' La siguiente sentencia llama al método de instancia.
    example.exampleMethod(arg2)
End Sub

Ahora, invierta los tipos de datos de los parámetros en los dos métodos: continuará pronto...

respag
Panamá - © 2012
Haga su donación para colaborar con La Escuela del Programador

La Escuela del Programador es un sitio web sin anuncios, sin ánimo de lucro, no es un sitio web comercial. Es el sueño de compartir con todos, muchos años de una gran pasión. Si realmente encuentra este sitio útil y lo aprovecha, le pido su generosa y no importa cuán modesta colaboración, simplemente para afrontar los costos de mantener este sitio disponible en internet.
No deseo lucrar con este sitio, ya que lo hago desinteresadamente, sólo le pido que, si puede, aporte (desde un dólar hasta lo que crea que puede dar), para afrontar los costos de dominio y hosting. Muchísimas gracias y ojalá juntos podamos hacer un sitio que sea una buena fuente de aprendizaje de programacíon en español.

Si no se siente en condiciones de colaborar, igualmente será bienvenido al sitio, es libre para todos y será un placer que encuentre mis artículos provechosos, pero si realmente me ayuda con una donación minima, seguramente, colaborará para que La Escuela del Programador se mantenga en la Web y crezca, conviertiendo a este sitio hecho con mucha pasión, dedicación y esfuerzo, en una buena fuente de aprendizaje.

Mis saludos cordiales y gracias por interesarse en mi sitio.

Rubén E. Spagnuolo
respag
Panamá - © 2012