Articulo de referencia

Método de extensión

En la programación orientada a objetos , un método de extensión es un método que se añade a un objeto después de que el objeto original haya sido compilado . El objeto modificad...

En la programación orientada a objetos , un método de extensión es un método que se añade a un objeto después de que el objeto original haya sido compilado . 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 orientada a objetos. No existe diferencia sintáctica entre llamar a un método de extensión y llamar a un método declarado en la definición del tipo. [ 1 ]

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

Soporte en lenguajes de programación

Los métodos de extensión son características de numerosos lenguajes, incluidos C# , Java ( a través de Manifold , Lombok o Fluent) , Gosu , JavaScript , Oxygene , Ruby , Smalltalk , Kotlin , Dart , Visual Basic.NET y Xojo . En lenguajes dinámicos como Python , el concepto de 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, con el primer argumento siendo de la clase extendida y precedido por thisla palabra clave "". Se han propuesto métodos de extensión para su inclusión en C++ y se han citado los métodos de extensión al estilo de C# como inspiración. [ 2 ]

En Java, los métodos de extensión se agregan mediante Manifold , un archivo JAR que se añade al classpath del proyecto. De forma similar a C#, un método de extensión de Java se declara como estático en una clase @Extension, donde el primer argumento tiene el mismo tipo que la clase extendida y está anotado con @Extension @This. Como alternativa, el plugin Fluent permite llamar a cualquier método estático como 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, entre asteriscos. Por ejemplo, cuando el código de la aplicación Etoys extiende clases de 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 específica del lenguaje para la extensión, ya que Ruby permite reabrir las clases en cualquier momento con la classpalabra clave para añadir nuevos métodos. La comunidad de Ruby suele describir un método de extensión como una especie de " monkey patch" . También existe una característica más reciente para añadir extensiones seguras/locales a los objetos, llamada Refinements , pero se sabe que se utiliza menos.

En Rust , no existen métodos de extensión; sin embargo, para los tipos que define el usuario, se pueden agregar arbitrariamente nuevos métodos con implbloques en cualquier punto impl Trait. Es ilegal colocar implbloques en tipos que no pertenecen al usuario, o si el rasgo o al menos uno de los tipos no está definido en el crate actual (lo que se conoce como la "regla de huérfanos"). [ 3 ] Esto se utiliza para evitar que dos crates implementen el mismo rasgo para el mismo tipo de forma incompatible.

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

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

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

usando el sistema ;Console.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 implementar características una sola vez de forma que posibiliten su reutilización sin necesidad de herencia ni de la sobrecarga de las invocaciones de métodos virtuales , o bien exigen a quienes implementan una interfaz que implementen funcionalidades triviales o excesivamente complejas.

Un escenario particularmente útil se da cuando la función opera sobre 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, como suele ocurrir en las bibliotecas que ofrecen a los desarrolladores una arquitectura de complementos o una funcionalidad similar.

Consideremos el siguiente código y supongamos que es el único código contenido en una biblioteca de clases. Sin embargo, cualquier implementador de la interfaz ILogger podrá escribir una cadena formateada simplemente incluyendo una instrucción `using MyCoolLogger` , sin necesidad de implementarla una sola vez y sin tener que crear una subclase de la implementación de ILogger proporcionada por la biblioteca de clases.

espacio de nombres Wikipedia.Util.Logging ;usando el sistema ;interfaz pública ILogger { void Write ( cadena texto ); }public static class LoggerExtensions { public static void Write ( this ILogger logger , string format , params object [] args ) { if ( logger != null ) { logger . Write ( String . Format ( format , args )); } } }

usar como  :

usando MyProject.Util.Logging ;MyLoggerImplementation logger = new (); logger . Write ( "{0}: {1}" , "kiddo sais" , "Mam mam mam mam..." ); logger . Write ( "{0}: {1}" , "kiddo sais" , "Ma ma ma ma... " ); logger . Write ( "{0}: {1}" , "kiddo sais" , "Mama mama mama mama " ); logger . Write ( "{0}: {1}" , "kiddo sais" , "Mamma mamma mamma ... " ); logger . Write ( "{0}: {1}" , "kiddo sais" , "Elisabeth Lizzy Liz..." ); logger . Write ( "{0}: {1}" , "mamma sais" , "¡¿QUÉ?!?!!!" ); logger . Escribe ( "{0}: {1}" , "kiddo sais" , "hola." );

Mejor acoplamiento suelto

Los métodos de extensión permiten a los usuarios de bibliotecas de clases evitar declarar argumentos, variables o cualquier otro elemento con un tipo perteneciente a dicha 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. Tras implementar cuidadosamente las conversiones y las fábricas, el cambio de una biblioteca de clases a otra puede simplificarse a la simple modificación de la instrucción `using` que habilita los métodos de extensión para que el compilador pueda enlazarlos.

Interfaces fluidas para programadores de aplicaciones

Los métodos de extensión son especialmente útiles para implementar las llamadas interfaces fluidas. Un ejemplo es la API de configuración de Entity Framework de Microsoft , que permite, por ejemplo, escribir código que se asemeja al inglés estándar en la medida de lo posible.

Se podría argumentar que esto es igualmente posible sin métodos de extensión, pero en la práctica se comprobará que los métodos de extensión ofrecen 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 base de datos Lists, definiendo una clave primaria y una clave foránea . El código se puede entender, más o menos, como: "Una lista TodoList tiene la clave TodoListID, su nombre de conjunto de entidades es Lists y tiene muchos elementos TodoItem, cada uno de los cuales tiene una lista TodoList requerida".

espacio de nombres Wikipedia.Ejemplos ;public class TodoItemContext : DbContext { public DbSet < TodoItem > TodoItems { get ; set ; } public DbSet < TodoList > TodoLists { get ; set ; }protected override void OnModelCreating ( DbModelBuilder modelBuilder ) { base.OnModelCreating ( modelBuilder ) ; modelBuilder.Entity <TodoList> ( ) .HasKey ( e = > e.TodoListId ) .HasEntitySetName ( " Lists " ) . HasMany ( e = > e.Todos ) .WithRequired ( e = > e.TodoList ) ; } }

Productividad

Consideremos, por ejemplo, IEnumerable y su simplicidad: un único método constituye la base de LINQ. Existen numerosas implementaciones de esta interfaz en Microsoft .NET. Habría sido engorroso exigir a cada una de ellas que implementara la serie de métodos definidos en el espacio de nombres System.Linq para operar con IEnumerable. Esto habría obligado a otras entidades, además de Microsoft, a implementar también dichos métodos, lo cual habría sido ineficiente dada la amplia utilización de esta interfaz. Al implementar un único método de esta interfaz, LINQ puede utilizarse prácticamente de inmediato. En 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.

espacio de nombres Wikipedia.Ejemplos ;using System ; using System.Collections.Generic ;public class BankAccount : IEnumerable < decimal > { private List < Tuple < DateTime , decimal >> credits ; // se asume que todos son negativos private List < Tuple < DateTime , decimal >> debits ; // se asume que todos son positivospublic IEnumerator < decimal > GetEnumerator () { IEnumerator < decimal > query = from dc in debits . Union ( credits ) orderby dc . Item1 // Fecha select dc . Item2 ; // Importe foreach ( decimal 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 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 IEnumerableespecífica para matrices (en System.SZArrayHelper), que elegirá automáticamente para las llamadas a métodos de extensión en referencias de tipo matriz, ya que su argumento será más específico ( this T[] value) que el método de extensión con el mismo nombre que opera en instancias de la IEnumerableinterfaz ( this IEnumerable value).

Aliviando la necesidad de una clase base común.

Con las clases genéricas, los métodos de extensión permiten implementar un comportamiento disponible para todas las instancias del tipo genérico sin necesidad de que hereden de una clase base común y sin restringir los parámetros de tipo a una rama de herencia específica. Esto resulta ventajoso, ya que las situaciones en las que se cumple este argumento requieren que una clase base no genérica implemente la característica compartida, lo que a su vez obliga a la subclase genérica a realizar conversiones de tipo (boxing o conversiones) cuando el tipo utilizado sea uno de los argumentos de tipo.

Uso conservador

En cuanto a la preferencia por los métodos de extensión sobre 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 autocompletado 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 selectivamente. Alternativamente, deberían definirse en un tipo lo suficientemente específico para que el método aparezca en IntelliSense solo cuando sea muy relevante y, teniendo en cuenta lo anterior, podría ser difícil encontrarlos si el desarrollador los espera, pero no los encuentra en IntelliSense debido a la falta de una instrucción `using`, ya que el desarrollador podría no haber asociado el método con la clase que lo define, ni siquiera con el espacio de nombres en el que reside, sino más bien con el tipo que extiende y el espacio de nombres en el que reside ese tipo.

El problema

En programación, surgen situaciones en las que es necesario añadir funcionalidad a una clase existente, por ejemplo, añadiendo un nuevo método. Normalmente, el programador modificaría el código fuente de la clase existente , pero esto obliga al programador a recompilar todos los binarios con estos nuevos cambios y requiere que el programador pueda modificar la clase, lo cual no siempre es posible, por ejemplo, al usar clases de un ensamblado de terceros . Esto suele solucionarse de tres maneras, todas ellas algo limitadas y poco intuitivas:

  1. Hereda la clase y luego implementa la funcionalidad en un método de instancia en la clase derivada.
  2. Implementa la funcionalidad en un método estático añadido a una clase auxiliar .
  3. Utilice la agregación en lugar de la herencia .

Soluciones actuales de C#

La primera opción es, en principio, más sencilla, pero tiene limitaciones, ya que muchas clases restringen la herencia de ciertos miembros o la prohíben por completo. Esto incluye las clases selladas y los distintos tipos de datos primitivos de C#, como int , float y string . Otra opción no presenta estas restricciones, pero puede resultar menos intuitiva, puesto que requiere una referencia a una clase independiente en lugar de utilizar directamente los métodos de la clase en cuestión.

Como ejemplo, consideremos la necesidad de extender la clase string con un nuevo método reverse cuyo valor de retorno sea una cadena con los caracteres en orden inverso. Dado que la clase string 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 manejar a medida que aumenta la biblioteca de métodos y clases de utilidad, especialmente para los principiantes. 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 que estaría en una clase diferente. Por lo tanto, una sintaxis mejor sería la siguiente:

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

Soluciones VB.NET actuales

En muchos aspectos, la solución en VB.NET es similar a la solución en C# anterior. Sin embargo, VB.NET tiene la ventaja de que permite pasar miembros 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 ()

Dado que Visual Basic permite pasar el objeto de origen por referencia, es posible modificarlo directamente sin necesidad de crear otra variable. Además, resulta más intuitivo, ya que funciona de forma coherente con los métodos existentes de las clases.

Métodos de extensión

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

espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;public static class Utility { public static string Reverse ( this string input ) { char [] chars = input . ToCharArray (); Array . Reverse ( chars ); return new String ( 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 como parámetro porque ya se conoce como el objeto que realiza la llamada (el objeto que aparece antes del punto).

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

Con métodos estáticos
HelperClass.Operation2 ( HelperClass.Operation1 ( 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, una clase puede tener tanto un método de instancia como un método de extensión con la misma firma. En tal caso, se prefiere el método de instancia al método de extensión. Ni el compilador ni el IDE de Microsoft Visual Studio advierten sobre este conflicto de nombres. Considere esta clase de C#, donde el GetAlphabet()método se invoca en una instancia de esta clase:

espacio de nombres Wikipedia.Ejemplos ;usando el sistema ;clase AlphabetMaker { // Cuando se implementa este método, ocultará la implementación en la clase ExtensionMethods. public void GetAlphabet () { Console . WriteLine ( "abc" ); } }static class ExtensionMethods { //Esto solo se llamará si no hay ningún método de instancia con la misma firma. public static void GetAlphabet ( this AlphabetMaker am ) { Console . WriteLine ( "ABC" ); } }

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

abecedario

El 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. Coe, Jonathan; Orr, Roger. "Métodos de extensión para C++" (PDF) . open-std.org . WG21 . Consultado el 4 de febrero de 2025 .
  3. The Rust Reference (25 de febrero de 2025). "Implementaciones" . doc.rust-lang.org . The Rust Reference.
  4. "Extensiones – El lenguaje de programación Swift (Swift 5.7)" . docs.swift.org . Consultado el 12 de junio de 2022 .
  • Colección de código abierto de bibliotecas de métodos de extensión de C# . Ahora archivada en Codeplex.
  • Método de extensión en C#
  • Métodos de extensión
  • Métodos de extensión de C# . Una colección.
  • extensionmethod.net Base de datos grande con métodos de extensión en C#, Visual Basic, F# y Javascript.
  • Explicación y ejemplo de código
  • Definir tus propias funciones en jQuery
  • Sintaxis uniforme para llamadas a funciones
  • Métodos de extensión en C#
  • Métodos de extensión en Java con Manifold
  • Métodos de extensión en Java con Lombok
  • Métodos de extensión en Java con Fluent
  • Funciones de extensión en Kotlin
Obtenido de " https://en.wikipedia.org/w/index.php?title=Extension_method&oldid=1340525475 "