.NET 3.5: Métodos de extensión (Extension methods)
Friday, January 8, 2010 12:16:29 AM
Una vez, en un programa que estaba haciendo, quise eliminar una parte de un texto, y no sé por qué, estuve buscando una función Delete para la variable de tipo string, porque yo juraba que existía. Algo como esto:
string texto = "Cadena de texto"; texto = texto.Delete(" de texto"); // texto = "Cadena"
Pero el método Delete no existía, sino que tenía que usar el de Replace, y reemplazar lo que quería por una cadena vacía. En realidad es algo sencillo, pero pues uno busca comodidad, y ahí sí me falló .NET 
Así como esta tontería, hay ocasiones en las que sí necesitamos una función para cierto tipo de objetos que no incluye el Framework. Como solución, podíamos crear funciones estáticas, enviarle el objeto como parámetro y que la función regresara el valor que esperábamos. Otra forma era crear clases heredadas y añadirle las funciones que necesitábamos. Pues a partir de la versión 3.5 de .NET, podemos crear funciones para las clases y tipos de datos ya definidos. Esto es lo que se conoce como métodos de extensión.
Nota: Los métodos de extensión no funcionan en los lenguajes usados en el Framework .NET 2.0 (Visual Studio 2005). Son únicamente para los de .NET 3.5 (VS 2008) o superior.
Retomando el ejemplo anterior, podemos crear una función Delete para el tipo string.
Primero les mostraré cómo crear un método estático, ya que de ahí nos basaremos para crear la extensión.
Como aclaración para los que no lo sepan, las funciones o métodos estáticos, en la programación orientada a objetos, son los que no dependen de un objeto, sino que pueden ser invocados directamente desde la clase. .NET ya incluye muchos de estos, como en éste ejemplo:
string[] hm = new string[] { "hola", "mundo" }; string hm2 = string.Join("-", hm); // hm2 = "hola-mundo"
Join, método estático de la clase String, une los datos de un arreglo de strings separados por lo indicado en el primer parámetro. Si no fuera un método estático, se tendría que hacer algo como hm.Join("-"), ya que debería ser invocado desde esa variable. Este último es el caso de los métodos de extensión, como veremos más adelante.
Ahora sí, comenzamos con nuestro método. Crearemos una clase independiente en la que introduciremos nuestro método de ejemplo:
C#
public class Extensiones { public static string Delete(string value, string delete) { return value.Replace(delete, ""); } }
Sí, es algo sin chiste, pero se entiende u.u
VB.NET
Public Class Extensiones Public Shared Function Delete(value As String, delete As String) As String Return value.Replace(delete, "") End Function End Class
En C#, la palabra static determina una función o variable estática. En VB.NET, se usa Shared.
Para implementar este método, haríamos esto:
string texto = Extensiones.Delete("Hola mundo!", " mundo");
La variable texto tendrá el valor "Hola!", pues " mundo" se eliminará. No tuvimos que crear una instancia de Extensiones, pues Delete es estático.
Sigue siendo mucho rollo para lo que queremos hacer, por lo que el agregar la función Delete al tipo String será la mejor solución.
Para aplicar un método de extensión, no basta con hacer estática sólo la función, sino toda la clase. Éste será el primer paso. Aquí la diferencia entre los lenguajes es más marcada:
C#
public static class Extensiones { }
VB.NET
Module Extensiones End Module
¡Éitale! ¿Qué pasó aquí? En C#, una clase puede ser definida como estática, pero en Visual Basic, no. Esto tiene que ver con la forma en que se programaba en VB6 y anteriores, que no sé bien cómo era, pero tiene características que se han mantenido. Para este lenguaje se crea un módulo, que tendrá nuestra función.
Nuestro método también será diferente en ambos casos, así que los pondré con su explicación por separado:
C#
public static class Extensiones { public static string Delete(this string value, string delete) { return value.Replace(delete, ""); } }
La diferencia aquí fue el this en el primer parámetro del método. Esto indicará que el primer parámetro será el objeto de referencia, al que se aplicará el método. Se invocará de esta manera:
string texto = "Hola mundo!"; string texto2 = texto.Delete(" mundo"); // texto2 = "Hola!"
¿Por qué sólo envío un parámetro al método Delete? Porque el parámetro que lleva la expresión this no es considerado como tal. Al entrar a Delete, value será texto, y delete será " mundo".
VB.NET
Imports System.Runtime.CompilerServices Module Extensiones <Extension()> _ Public Function Encriptar(ByVal value As String, ByVal delete As String) As String Return value.Replace(delete, "") End Function End Module
Primera diferencia: Se debe agregar el atributo Extension a la función, el cual se encuentra en el espacio System.Runtime.CompilerServices (por eso importé la biblioteca, aunque también pude haber escrito la ruta en el atributo). Éste indicará que la función es un método de extensión.
Segunda: No necesita la palabra this, o mejor dicho, su equivalente en VB, Me, por lo mismo de haber indicado el atributo Extension.
Igual que en C#, el primer parámetro será el objeto que invoque al método:
Dim texto As String = "Hola mundo!" Dim texto2 As String = texto.Delete(" mundo") ' texto2 = "Hola!"
Los métodos de extensión no tienen que devolver un valor, necesariamente. Ni el valor devuelto debe ser del mismo tipo del objeto de referencia.
C#
// Método que muestra una ventana con el mensaje de la Exception public static void ShowErrorMessage(this Exception ex) { // Este método invoca al otro método de extensión, FullErrorMessage() MessageBox.Show(ex.FullErrorMessage(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } // Método que genera un string de la excepción y sus excepciones internas. // Si el objeto contiene otra excepción interna, se invoca a éste mismo método para dicha excepción. public static string FullErrorMessage(this Exception ex) { string msg = ex.Message; if (ex.InnerException != null) msg += "\n" + ex.InnerException.FullErrorMessage(); return msg; }
VB.NET
' Método que muestra una ventana con el mensaje de la Exception Public Shared Sub ShowErrorMessage(ex As Exception) ' Este método invoca al otro método de extensión, FullErrorMessage() MessageBox.Show(ex.FullErrorMessage(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) End Sub ' Método que genera un string de la excepción y sus excepciones internas. ' Si el objeto contiene otra excepción interna, se invoca a éste mismo método para dicha excepción. Public Static Function FullErrorMessage(ex As Exception) Dim msg As String = ex.Message If ex.InnerException IsNot Nothing Then _ msg &= VbCrLf & ex.InnerException.FullErrorMessage() Return msg End If
Si usamos .NET 2 (Visual Studio 2005), hay que quitar la propiedad estática a la clase, quitar el this en los métodos e implementarlo como una función estática normal, como en el primer ejemplo.
EDITADO:
Se me había pasado algo (gracias, Aníbal). Un método de extensión afecta tanto a la clase que se le indica como a todas las clases que heredan de ésta. Por ejemplo, si estamos trabajando para un sitio Web, y creamos un método de extensión para WebControl, éste método se implementará en todas las instancias de TextBox, Button, GridView, Label, etc., pues todas se derivan de aquella. Si creamos el método para la clase Control, se implementará no sólo en los derivados de WebControl, sino también en otros como los HtmlControls (HtmlAnchor, HtmlButton, HtmlImage, HtmlGenericControl).
Si creamos un método de extensión para la clase object, se implementará en TODAS las clases y tipos de datos del lenguaje, ya sean instancias de controles, como los mencionados, o tipos int, string, char, bool, etc., e incluso enumeradores.











Anibalanibal784 # Friday, January 8, 2010 7:15:43 PM
una clase se crea con:
@interface NombreClase : Padre { // atributos } @end @implementation NombreClase @endSe extiende el funcionamiento de la clase mediante categorías (Por ejemplo, NSString OpenStep):
@interface NSString (DeleteCategory) - (NSString*)delete: (NSString*)filtro; @end @implementation NSString (DeleteCategory) - (NSString*)delete: (NSString*)filtro { // implementacion } @end(a ésta altura, mejor escribo un post), es bastante natural para el lenguaje (y para el programador) extender el funcionamiento de una clase (siempre y cuando se conozca cómo se declara/implementa una clase, cosa que no sucede con C#).
Momento que no termina ahí, si bien en python 1991(hasta donde se), no se puede extender una clase built-in (object, string, int, etc.), si se puede hacer con una clase distinta:
def func(self, parm1, parm2): # codigo python Objeto.delete = funco algo así, hace rato que no toco a ese nivel python.
Oh!, aún hay otro más, ruby 1995, se puede extender una clase con aún más facilidad:
class string def delete(filtro) # codigo end endY llegamos a 2008, donde un lenguaje "moderno" como lo es C# no logra la simplicidad de un lenguaje abuelo de 1980 (Objective-C).
Crítica aparte, el post está muy bueno y bien explicado.
el Israel-isra # Friday, January 8, 2010 8:04:48 PM
La mayor crítica contra los lenguajes "modernos", como los del .NET Framework, y herramientas como Visual Studio, es que simplifican la programación, haciéndonos cada vez menos programadores. Ése también es un punto de vista.
A mí me sigue pareciendo algo sencillo como se hace en C#, aunque no sea taaaan simple como en don Objective-C. Pero es mi muy particular forma de verlo.
Si supiera algo de C tal vez publicaría, pero nunca lo aprendí, malamente, ni me di tiempo para estudiarlo
Anibalanibal784 # Saturday, January 9, 2010 2:05:51 AM
C -> C++ -> C#.... ahí tienes un derivado de C
En realidad la crítica no viene hacia los lenguajes modernos, los cuales no simplifican la programación (más allá de recolector de basura, no existe gran aporte de lenguajes modernos), sino los frameworks, son lo que facilitan y bastante, con unas simples líneas de código puedes hacer un parser xml.
el Israel-isra # Saturday, January 9, 2010 4:40:44 PM
Originally posted by anibal784:
Me refería a ninguno aparte de ése ¬¬' xD
Yo creo que el problema no es tanto que los frameworks nos faciliten el trabajo, sino que a muchos ya no se nos enseña programación más elemental. Lo muy poco que vi en la escuela fue System.out.print() en Java, mucho de Fox
Saludos
Unregistered user # Saturday, January 9, 2010 10:59:23 PM
Carlos RazoElRazo # Monday, January 11, 2010 2:09:18 AM
el Israel-isra # Monday, January 11, 2010 4:45:37 PM
Y sí, supuestamente llevábamos una materia de ensamblador, pero según el profesor, la coordinación decidió que no viéramos esa materia, pues no teníamos las bases (aunque la materia que dijo sí la habíamos llevado), y nos enseñó lo único que sabía usar: Visual FoxPro 6, ni siquiera el 9.
Claro que ni lo pongo en mi currículum. No vaya a ser que por ese lenguaje me contraten
Y lo de .NET no cuenta. Fue una semana (a la cual afortunadamente no fui por estar exento
Barraco Mármol Jerónimojerobarraco # Monday, January 11, 2010 9:21:40 PM
Originally posted by ElRazo:
xD
la verdad que espero nunca tener que usar .net, pero es bueno ir aprendiendo cosas. thanks
yo uso python y si, es extensible por naturaleza.
creo que es algo que viene _un poco_ por el lado del concepto de herencia. (y objetos)
Barraco Mármol Jerónimojerobarraco # Monday, January 11, 2010 9:22:07 PM
Anibalanibal784 # Monday, January 11, 2010 10:23:01 PM
public static void save(this object obj) { File f = open(path_to_file); f.write(obj.toString()); f.close(); }Entonces, por ejemplo yo puedo hacer en mi código algo como:
Notar que el método de extensión (aunque a lo mejor sintácticamente incorrecto) lo que haría sería una implementación sobre object, y el objeto instanciado es del tipo string, string ¿tiene acceso al método save o no?
python es un gran lenguaje, pero hay algo que te limita, no permite la extensión sobre tipos built-in:
No, viene más bien por el lado de comodidad y poder extender lo que hizo otro sin necesidad de recurrir a la herencia. Es más como dijo el-isra, con ésto uno puede hacer:
lbl.Text = lbl.Text.Delete("Hola");en lugar de su contraparte de herencia:
SomeClass tmp = new SomeClass(lbl.Text); lbl.Text = tmp.Delete("Hola");el Israel-isra # Monday, January 11, 2010 10:36:41 PM