My Opera is closing 3rd of March
photo of el Isra

el blog del isra

.NET 3.5: Métodos de extensión (Extension methods)

, ,

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 sad

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.

Cómo eliminar la publicidad de Windows Live Messenger 2009Historia de Hermosillo

Comments

Anibalanibal784 Friday, January 8, 2010 7:15:43 PM

Objective C (año 1980)
una clase se crea con:
@interface NombreClase : Padre
{
    // atributos
}
@end

@implementation NombreClase
@end

Se 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 = func

o 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
end


Y 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

Pues no conozco Objective-C (ni ningún otro derivado de C, desgraciadamente), ni Python, ni mucho menos Ruby, pero eso de la simplicidad es una opinión tuya.
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 sad

Anibalanibal784 Saturday, January 9, 2010 2:05:51 AM


Pues no conozco Objective-C (ni ningún otro derivado de C, desgraciadamente), ni Python, ni mucho menos Ruby, pero eso de la simplicidad es una opinión tuya.


C -> C++ -> C#.... ahí tienes un derivado de C bigsmile.
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:

C -> C++ -> C#.... ahí tienes un derivado de C


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 yuck Pro y ASP (no ASP.NET), y jamás vimos C, Pascal, ni nada de eso. Pero si supiera programar en un lenguaje viejo, y me ponen un framework que me evita trabajo, mejor para mí. Siempre y cuando sí sepa programar sin necesidad de estas herramientas.

Saludos

Unregistered user Saturday, January 9, 2010 10:59:23 PM

Eddie Bo. writes: Shhh visual fox pro es la mata hahahahahahhaha obviamente es un sarcasmo XDDDDDDDD te acuerdas cuando burgos nos dio visual fox pro en lugar de ensamblador XDDDD estuvo curado de momento pero la neta me hubiera gustado mejor aprender C y luego el .NET pero bien aprendido no como nos enseño entre comillas por supuesto la yolanda hahahaahahah aun asi extraño la escuela (el cotorreo claro) en fin... haber si aprendo C algun dia XD y actualizarme al .NET 3.5 XD de hecho tengo instalado el visual studio 2008 pero ni lo he checado jajajaajjaj tendre que darme tiempo

Carlos RazoElRazo Monday, January 11, 2010 2:09:18 AM

por que escribes esooo!!!, nadie debe saber que nos dieron foxpro u.u

el Israel-isra Monday, January 11, 2010 4:45:37 PM

Cualquier egresado del ITH que conoce a ese maestro sabe que vimos FoxPro u.u
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 lol
Y lo de .NET no cuenta. Fue una semana (a la cual afortunadamente no fui por estar exento cool ) de ver los distintos tipos de errores de compilación en una aplicación.

Barraco Mármol Jerónimojerobarraco Monday, January 11, 2010 9:21:40 PM

Originally posted by ElRazo:

nadie debe saber que nos dieron foxpro u.u


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

ah y falto el ejemplo con javascript , se usa la palabra prototype

Anibalanibal784 Monday, January 11, 2010 10:23:01 PM

Pregunta: los métodos de extensión ¿están disponible para las clases heredadas o únicamente para objetos creados de dicha clase?. Me explico, supongamos que quiero crear un método para hacer un objeto persistente, con muchos lenguajes debo crear un árbol completo de herencia con una raiz o algo parecido, el problema con ésta solución es que no puedo persistir objetos que ya vienen en el lenguaje (como ser la clase string), un ejemplo facilito (mezclado pseudocódigo) sería algo así:
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:
string s = "Hola mundo";

// hacer algo con el string
s.save();

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?


yo uso python y si, es extensible por naturaleza.


python es un gran lenguaje, pero hay algo que te limita, no permite la extensión sobre tipos built-in:
>> def printf(self):
....    pass
....
>> str.printf = printf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'str'


creo que es algo que viene _un poco_ por el lado del concepto de herencia. (y objetos)


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

@Anibal: Los métodos de extensión se aplican para una clase y todas sus heredadas. Se me pasó ese detalle. Y el ejemplo que das con object es muy bueno. Anexaré esa información al artículo. Gracias

Write a comment

New comments have been disabled for this post.