stringtranslate.com

Método de extensión

En la programación informática orientada a objetos , un método de extensión es un método añadido a un objeto después de que se compilara el objeto original . El objeto modificado suele ser una clase, un prototipo o un tipo. Los métodos de extensión son características de algunos lenguajes de programación orientados a objetos. No existe ninguna diferencia sintáctica entre llamar a un método de extensión y llamar a un método declarado en la definición de tipo. [1]

Sin embargo, no todos los lenguajes implementan los métodos de extensión de una manera igualmente segura. Por ejemplo, lenguajes como C#, Java (a través de Manifold, Lombok o Fluent) y Kotlin no alteran la clase extendida de ninguna manera, porque al hacerlo pueden romper las jerarquías de clases e interferir con el envío de métodos virtuales. En cambio, estos lenguajes implementan estrictamente los métodos de extensión de forma estática y utilizan el envío estático para invocarlos.

Soporte en lenguajes de programación

Los métodos de extensión son características de numerosos lenguajes, entre ellos C# , Java a través de Manifold o Lombok o Fluent, Gosu , JavaScript , Oxygene , Ruby , Smalltalk , Kotlin , Dart , Visual Basic.NET y Xojo . En lenguajes dinámicos como Python , el concepto de un método de extensión es innecesario porque las clases (excluyendo las clases integradas) se pueden extender sin ninguna sintaxis especial (un enfoque conocido como " monkey-patching ", empleado en bibliotecas como gevent).

En VB.NET y Oxygene, se reconocen por la presencia de la extensionpalabra clave o atributo " ". En Xojo, la Extendspalabra clave " " se utiliza con métodos globales.

En C#, se implementan como métodos estáticos en clases estáticas, donde el primer argumento es de clase extendida y está precedido por thisla palabra clave " ".

En Java, los métodos de extensión se agregan a través de Manifold, un archivo jar agregado a la ruta de clases del proyecto. De manera similar a C#, un método de extensión de Java se declara estático en una clase @Extension donde el primer argumento tiene el mismo tipo que la clase extendida y se anota con @This. De manera alternativa, el complemento Fluent permite llamar a cualquier método estático como un método de extensión sin usar anotaciones, siempre que la firma del método coincida.

En Smalltalk, cualquier código puede agregar un método a cualquier clase en cualquier momento, enviando un mensaje de creación de método (como methodsFor:) a la clase que el usuario desea extender. La categoría de método de Smalltalk se nombra convencionalmente según el paquete que proporciona la extensión, rodeada de asteriscos. Por ejemplo, cuando el código de la aplicación Etoys extiende clases en la biblioteca principal, los métodos agregados se colocan en la *etoys*categoría.

En Ruby, al igual que en Smalltalk, no existe una característica especial del lenguaje para las extensiones, ya que Ruby permite reabrir las clases en cualquier momento con la classpalabra clave para agregar nuevos métodos. La comunidad Ruby suele describir un método de extensión como una especie de parche de mono . También existe una característica más nueva para agregar extensiones seguras/locales a los objetos, llamada Refinamientos, pero se sabe que se usa menos.

En Swift, la extensionpalabra clave marca una construcción similar a una clase que permite la adición de métodos, constructores y campos a una clase existente, incluida la capacidad de implementar una nueva interfaz/protocolo para la clase existente. [2]

Métodos de extensión como característica habilitadora

Además de los métodos de extensión que permiten extender el código escrito por otros, como se describe a continuación, los métodos de extensión también permiten patrones que son útiles por sí mismos. La razón principal por la que se introdujeron los métodos de extensión fue Language Integrated Query (LINQ). La compatibilidad del compilador con los métodos de extensión permite una integración profunda de LINQ con el código antiguo, al igual que con el nuevo, así como la compatibilidad con la sintaxis de consulta , que por el momento es exclusiva de los principales lenguajes de Microsoft .NET .

Consola . WriteLine ( new [] { Math . PI , Math . E }. Where ( d => d > 3 ). Select ( d => Math . Sin ( d / 2 )). Sum ()); // Salida: // 1            

Centralizar el comportamiento común

Sin embargo, los métodos de extensión permiten que las características se implementen una sola vez de maneras que permiten la reutilización sin la necesidad de herencia o la sobrecarga de invocaciones de métodos virtuales , o para requerir que los implementadores de una interfaz implementen una funcionalidad trivial o lamentablemente compleja.

Un escenario particularmente útil es si la característica opera en una interfaz para la cual no existe una implementación concreta o el autor de la biblioteca de clases no proporciona una implementación útil, por ejemplo, como suele suceder en las bibliotecas que proporcionan a los desarrolladores una arquitectura de complemento o una funcionalidad similar.

Considere el código siguiente y suponga que es el único código contenido en una biblioteca de clases. Sin embargo, cada implementador de la interfaz ILogger obtendrá la capacidad de escribir una cadena con formato, simplemente incluyendo una declaración using MyCoolLogger , sin tener que implementarla una vez y sin necesidad de crear una subclase de una implementación de ILogger proporcionada por una biblioteca de clases.

espacio de nombres MyCoolLogger ; Interfaz pública ILogger { void Write ( cadena texto ); }     clase pública estática LoggerExtensions { pública estática void Write ( este ILogger logger , cadena formato , parámetros objeto [] args ) { if ( logger != null ) logger . Write ( cadena . Format ( formato , args )); } }                      

Mejor acoplamiento flexible

Los métodos de extensión permiten a los usuarios de bibliotecas de clases abstenerse de declarar argumentos, variables o cualquier otra cosa con un tipo que provenga de esa biblioteca. La construcción y conversión de los tipos utilizados en la biblioteca de clases se pueden implementar como métodos de extensión. Después de implementar cuidadosamente las conversiones y las fábricas, cambiar de una biblioteca de clases a otra se puede hacer tan fácil como cambiar la declaración using que hace que los métodos de extensión estén disponibles para que el compilador los vincule.

Interfaces de programación de aplicaciones fluidas

Los métodos de extensión tienen un uso especial en la implementación de las denominadas interfaces fluidas. Un ejemplo es la API de configuración de Entity Framework de Microsoft, que permite, por ejemplo, escribir código que se parezca lo más posible al inglés normal.

Se podría argumentar que esto es igualmente posible sin métodos de extensión, pero se encontrará que en la práctica, los métodos de extensión proporcionan una experiencia superior porque se imponen menos restricciones a la jerarquía de clases para que funcione -y se lea- como se desea.

El siguiente ejemplo utiliza Entity Framework y configura la clase TodoList para que se almacene en la tabla de la base de datos Lists y define una clave principal y una clave externa. El código debe entenderse más o menos como: "Una TodoList tiene la clave TodoListID, su nombre de conjunto de entidades es Lists y tiene muchos TodoItem, cada uno de los cuales tiene una TodoList requerida".

clase pública TodoItemContext : DbContext { pública DbSet < TodoItem > TodoItems { obtener ; establecer ; } pública DbSet < TodoList > TodoLists { obtener ; establecer ; }                    anulación protegida void OnModelCreating ( DbModelBuilder modelBuilder ) { base.OnModelCreating ( modelBuilder ) ; modelBuilder.Entity < TodoList > ( ) . HasKey ( e = > e.TodoListId ) .HasEntitySetName ( " Listas " ) . HasMany ( e = > e.Todos ) .WithRequired ( e = > e.TodoList ) ; } }                   

Productividad

Consideremos, por ejemplo, IEnumerable y observemos su simplicidad: sólo hay un método, pero es más o menos la base de LINQ. Hay muchas implementaciones de esta interfaz en Microsoft .NET. Sin embargo, obviamente, habría sido complicado exigir que cada una de estas implementaciones implemente toda la serie de métodos que se definen en el espacio de nombres System.Linq para operar en IEnumerables, aunque Microsoft tiene todo el código fuente. Peor aún, esto habría requerido que todos los que consideraran usar IEnumerable también implementaran todos esos métodos, lo que habría sido muy antiproductivo considerando el uso generalizado de esta interfaz tan común. En cambio, al implementar el único método de esta interfaz, LINQ se puede usar más o menos de inmediato. Especialmente considerando que en prácticamente la mayoría de los casos el método GetEnumerator de IEnumerable se delega a la implementación GetEnumerator de una colección, lista o matriz privada.

public class CuentaBancaria : IEnumerable < decimal > { private List < Tuple < DateTime , decimal >> credits ; // se asumen todos los negativos private List < Tuple < DateTime , decimal >> debits ; // se asumen todos los positivos                public IEnumerator < decimal > GetEnumerator () { var consulta = from dc in debits . Union ( credits ) orderby dc . Item1 /* Fecha */ select dc . Item2 ; /* Importe */ foreach ( var amount in query ) yield return amount ; } } // dada una instancia de BankAccount llamada ba y usando System.Linq sobre el archivo actual, // ahora se podría escribir ba.Sum() para obtener el saldo de la cuenta, ba.Reverse() para ver primero las transacciones más recientes, // ba.Average() para obtener el importe promedio por transacción, etcétera, sin tener que escribir nunca un operador aritmético                             

Actuación

Dicho esto, se pueden agregar implementaciones adicionales de una característica proporcionada por un método de extensión para mejorar el rendimiento o para lidiar con implementaciones de interfaz implementadas de manera diferente, como proporcionar al compilador una implementación de IEnumerable específicamente para matrices (en System.SZArrayHelper), que elegirá automáticamente para llamadas al método de extensión en referencias tipificadas en matrices, ya que su argumento será más específico (este valor T[]) que el método de extensión con el mismo nombre que opera en instancias de la interfaz IEnumerable (este valor IEnumerable).

Aliviar la necesidad de una clase base común

Con las clases genéricas, los métodos de extensión permiten la implementación de un comportamiento que está disponible para todas las instancias del tipo genérico sin necesidad de que deriven de una clase base común y sin restringir los parámetros de tipo a una rama de herencia específica. Esto es una gran ventaja, ya que las situaciones en las que se cumple este argumento requieren una clase base no genérica solo para implementar la característica compartida, lo que luego requiere que la subclase genérica realice conversiones y/o conversiones siempre que el tipo utilizado sea uno de los argumentos de tipo.

Uso conservador

Se debe tener en cuenta que es preferible utilizar métodos de extensión en lugar de otros medios para lograr la reutilización y un diseño orientado a objetos adecuado. Los métodos de extensión pueden "saturar" las funciones de finalización automática de los editores de código, como IntelliSense de Visual Studio, por lo que deberían estar en su propio espacio de nombres para permitir que el desarrollador los importe de forma selectiva o deberían definirse en un tipo que sea lo suficientemente específico para que el método aparezca en IntelliSense solo cuando sea realmente relevante y, dado lo anterior, considere que pueden ser difíciles de encontrar si el desarrollador los espera, pero los pierde de IntelliSense debido a una declaración using faltante, ya que es posible que el desarrollador no haya asociado el método con la clase que lo define, o incluso con el espacio de nombres en el que se encuentra, sino con el tipo que extiende y el espacio de nombres en el que se encuentra ese tipo.

El problema

En programación, surgen situaciones en las que es necesario agregar funcionalidad a una clase existente, por ejemplo, agregando un nuevo método. Normalmente, el programador modificaría el código fuente de la clase existente , pero esto obliga al programador a volver a compilar todos los binarios con estos nuevos cambios y requiere que el programador pueda modificar la clase, lo que no siempre es posible, por ejemplo, cuando se utilizan clases de un ensamblado de terceros . Esto generalmente se soluciona de una de tres maneras, todas las cuales son algo limitadas y poco intuitivas [ cita requerida ] :

  1. Heredar la clase y luego implementar la funcionalidad en un método de instancia en la clase derivada.
  2. Implemente la funcionalidad en un método estático agregado a una clase auxiliar.
  3. Utilice agregación en lugar de herencia .

Soluciones actuales de C#

La primera opción es en principio más sencilla, pero lamentablemente está limitada por el hecho de que muchas clases restringen la herencia de ciertos miembros o la prohíben por completo. Esto incluye las clases selladas y los diferentes tipos de datos primitivos de C# como int , float y string . La segunda opción, por otro lado, no comparte estas restricciones, pero puede ser menos intuitiva ya que requiere una referencia a una clase separada en lugar de utilizar directamente los métodos de la clase en cuestión.

Como ejemplo, considere la necesidad de ampliar la clase de cadena con un nuevo método inverso cuyo valor de retorno es una cadena con los caracteres en orden inverso. Debido a que la clase de cadena es un tipo sellado, el método normalmente se agregaría a una nueva clase de utilidad de una manera similar a la siguiente:

cadena x = "algún valor de cadena " ; cadena y = Utility.Reverse ( x ) ;      

Sin embargo, esto puede volverse cada vez más difícil de navegar a medida que aumenta la biblioteca de métodos y clases de utilidad, en particular para los recién llegados. La ubicación también es menos intuitiva porque, a diferencia de la mayoría de los métodos de cadena, no sería un miembro de la clase de cadena, sino de una clase completamente diferente. Por lo tanto, una mejor sintaxis sería la siguiente:

cadena x = "algún valor de cadena" ; cadena y = x . Reverse ();      

Soluciones VB.NET actuales

En la mayoría de los aspectos, la solución VB.NET es similar a la solución C# anterior. Sin embargo, VB.NET tiene una ventaja única, ya que permite que los miembros se pasen a la extensión por referencia (C# solo lo permite por valor). Esto permite lo siguiente:

Dim x As String = "algún valor de cadena" x . Reverse ()     

Como Visual Basic permite pasar el objeto de origen por referencia, es posible realizar cambios en el objeto de origen directamente, sin necesidad de crear otra variable. También es más intuitivo, ya que funciona de manera coherente con los métodos de clases existentes.

Métodos de extensión

Sin embargo, la nueva característica del lenguaje de los métodos de extensión en C# 3.0 hace posible este último código. Este enfoque requiere una clase estática y un método estático, como se muestra a continuación.

clase pública estática Utility { cadena pública estática Reverse ( esta cadena entrada ) { char [] chars = input.ToCharArray ( ) ; Array.Reverse ( chars ) ; devolver nueva cadena ( chars ) ; } }                   

En la definición, el modificador 'this' antes del primer argumento especifica que se trata de un método de extensión (en este caso, del tipo 'string'). En una llamada, el primer argumento no se 'pasa' porque ya se conoce como el objeto que 'llama' (el objeto antes del punto).

La principal diferencia entre llamar a métodos de extensión y llamar a métodos auxiliares estáticos es que los métodos estáticos se llaman en notación de prefijo , mientras que los métodos de extensión se llaman en notación de infijo . Esto último genera un código más legible cuando el resultado de una operación se utiliza para otra operación.

Con métodos estáticos
HelperClass . Operación2 ( HelperClass . Operación1 ( x , arg1 ), arg2 )  
Con métodos de extensión
x . Operación1 ( arg1 ). Operación2 ( arg2 )

Conflictos de nombres en métodos de extensión y métodos de instancia

En C# 3.0, pueden existir tanto un método de instancia como un método de extensión con la misma firma para una clase. En tal escenario, se prefiere el método de instancia sobre el método de extensión. Ni el compilador ni el IDE de Microsoft Visual Studio advierten sobre el conflicto de nombres. Considere esta clase de C#, donde el GetAlphabet()método se invoca en una instancia de esta clase:

clase AlphabetMaker { public void GetAlphabet () { //Cuando se implementa este método, Console . WriteLine ( "abc" ); //hará sombra de la implementación } //en la clase ExtensionMethods. }            clase estática ExtensionMethods { public static void GetAlphabet ( this AlphabetMaker am ) { // Esto solo se llamará Console.WriteLine ( "ABC" ) ; //si no hay ninguna instancia } //método con la misma firma. }               

Resultado de invocar GetAlphabet()una instancia de AlphabetMakersi solo existe el método de extensión:

abecedario

Resultado si existen tanto el método de instancia como el método de extensión:

abecedario

Véase también

Referencias

  1. ^ "Métodos de extensión". Microsoft . Consultado el 23 de noviembre de 2008 .
  2. ^ "Extensiones: el lenguaje de programación Swift (Swift 5.7)". docs.swift.org . Consultado el 12 de junio de 2022 .

Enlaces externos