Inicio > .NET Framework, Apunte, C# > Métodos de extensiones

Métodos de extensiones


¿Alguna vez te has topado con una clase a la que te gustaría que incorporara algún método? Cuando esto pasa, lo primero que pensamos es: “¡ajajá! pues derivo una clase y listo”. Y al poner manos a la obra, te das cuenta de que la clase está sellada… ¿Qué es lo único que se te ocurre hacer? Pues en la clase donde necesitas ejecutar el ansiado método, te creas uno privado que tome como parámetro el valor a modificar. ¿Y que pasa si quieres utilizar dicho método en otros lugares? Comienzas a hacer copy-paste… o si eres más ordenado, pues no quedará más remedio que crear una clase estática, usualmente llamada “Util”, y ahí pegar el método estático.

Con lo descrito anteriormente, consideremos el siguiente escenario. En algún lugar de tu aplicación necesitas codificar un texto a base 64 y viceversa. ¿Qué haces? Algo así:

string str = "Hola mundo!";
byte[] buffer = Encoding.UTF8.GetBytes(str);
string encoded = Convert.ToBase64String(buffer);

Y para decodificar:

byte[] buffer = Convert.FromBase64String(encoded);
string decoded = Encoding.UTF8.GetString(buffer);

Pero resulta que esto lo utilizarás en diferentes lugares. ¡Cuánto desearías que la clase string se pudiera modificar! Pero como esto no es posible dado que ésta está sellada (y además resulta que hay muy buenas razones para ello), no te queda más que crearte un par de métodos estáticos:

static class TextExtensions
{
  public static string Encode64(string str)
  {
    byte[] buffer = Encoding.UTF8.GetBytes(str);
    return Convert.ToBase64String(buffer);
  }

  public static string Decode64(string str)
  {
    byte[] buffer = Convert.FromBase64String(str);
    return Encoding.UTF8.GetString(buffer);
  }
}

Y utilizarlo así:

string str = "Hola mundo!";
string encoded = TextExtensions.Encode64(str);
string decoded = TextExtensions.Decode64(encoded);

Pues resulta que la gente de Microsoft ideó una forma para hacer que lo anterior, aunque sea, fuese un poco más fácil para el programador, al menos en lo que a legibilidad se refiere. A este artilugio le llamaron “extension methods”, o métodos de extensión.

Un método de extensión no es otra cosa que lo que ya hicimos: una clase estática con métodos estáticos que toman como primer parámetro el valor que queremos modificar (en el ejemplo anterior, “str”) y opcionalmente algún otro parámetro que necesitemos. La diferencia es que cuando creamos un método de extensión, éste lo podemos invocar como si fuera un método más de la propia clase (i.e. el tipo de dato del primer parámetro, en nuestro ejemplo, “string”). Es decir, si Encode64 y Decode64 los hiciéramos métodos de extensión, podríamos haberlo utilizado de la siguiente forma:

string str = "Hola mundo!";
string encoded = str.Encode64();
string decoded = encoded.Decode64();

¡Dulce! ¿No? Y hablando de dulces… lo anterior, en efecto, no es otra cosa que “azúcar sintáctica”, es decir, una forma de abreviar nuestro código, ya que al compilar lo anterior el compilador lo traducirá a una llamada estática normal, es decir, a lo que teníamos en el listado anterior al último.

Por cierto, nota que no le pasamos parámetro alguno a Encode64. Esto es así porque el compilador automáticamente le pasa como primer parámetro la referencia/valor de la variable que ejecuta el método (en nuestro caso, a “str”). Por eso es que los métodos de extensión siempre deben tener al menos un parámetro.

Ahora bien, ¿cómo declaramos un método de extensión? Sencillo. Simplemente creamos nuestro método estático dentro de una clase estática y al primer parámetro le agregamos la palabra reservada “this” antes del tipo de dato del parámetro. Con respecto a nuestro ejemplo, éste quedaría así:

static class TextExtensions
{
  public static string Encode64(this string str)
  {
    byte[] buffer = Encoding.UTF8.GetBytes(str);
    return Convert.ToBase64String(buffer);
  }

  public static string Decode64(this string str)
  {
    byte[] buffer = Convert.FromBase64String(str);
    return Encoding.UTF8.GetString(buffer);
  }
}

Y listo, ahora sí ya podemos utilizar nuestro método de extensión. Por supuesto, aún declarándolo como tal es posible seguir utilizando ambos métodos en la forma tradicional (es decir, “TextExtensions.Encode64(“Hola mundo”);”.

Por supuesto, los métodos pueden tener más parámetros, en cuyo caso al ser invocados como extensión habrá que pasar el segundo en primer lugar, el tercer en segundo y así sucesivamente.

Por cierto, cabe mencionar que no se puede sobreescribir con un método de extensión algún método que la clase en cuesitón ya haya definido. Es decir, no pueden tener la misma firma. Por ejemplo, si intentara crear un método de extensión sobre la clase string, que se llamara “ToUpper”, regresara otro string y no tuviera parámetros, aunque el código compila, el método de extensión nunca sería invocado al hacer “str.ToUpper();”, sino que se invocaría el método ToUpper de la clase string. Obvio, si lo invocásemos de la forma tradicional (i.e. TextExtensions.ToUpper(str);), así sí que se llamaría (esta es la razón por la que el compilador no se queja). Así que hay que tener cuidado con el nombre y firma que le damos a nuestros métodos de extensión.

Otra cosa importante a considerar. Los métodos de extensión se deben de incluir en un espacio de nombres, y éstos no estarán disponibles a menos que nos encontremos en el mismo espacio de nombres, o bien lo importemos usando la sentencia “using”.

Ya por último, una curiosidad. Es posible invocar métodos de extensión sobre variables que son nulas, sin que se lance un NullReferenceException. Digo, por lo menos si tienes cuidado en revisar que el primer parámetro no sea nulo y realizar alguna actividad en caso de que así sea. En otras palabras, si la variable sobre la que se invoca es nula, el método de extensión recibirá como primer parámetro un null.

Dicho lo anterior, y ya para cerrar este apunte, dejo un ejemplo completo con siete métodos de extensión que operan sobre “string”: los dos ya mostrados, con una sobrecarga que permite especificar el tipo de codificación de texto a emplear, más un método que regresa una cadena de texto invertida, y un método que nunca será llamado como extensión. Además, el último método revisa si el valor de entrada es nulo, de tal suerte que es posible invocarlo desde una variable string nula.

using System;
using System.Text;

namespace Fermasmas.Wordpress.Com
{
  public static class TextExtensions
  {
    public static string Encode64(this string str)
    {
      return Encode64(str, Encoding.UTF8);
    }

    public static string Encode64(this string str, Encoding encoding)
    {
      byte[] buffer = encoding.GetBytes(str);
      return Convert.ToBase64String(buffer);
    }

    public static string Decode64(this string str)
    {
      return Decode64(str, Encoding.UTF8);
    }

    public static string Decode64(this string str, Encoding encoding)
    {
      byte[] buffer = Convert.FromBase64String(str);
      return encoding.GetString(buffer);
    }

    public static string Reverse(this string str)
    {
      StringBuilder builder = new StringBuilder();
      for (int i = str.Length - 1; i >= 0; i--)
        builder.Append(str[i]);
      
      return builder.ToString();
    }

    public static string ToUpper(this string str)
    {
      return "Este método nunca será invocado como extensión.";
    }

    public static string AppendGreeting(this string str)
    {
      string val;
      if (str != null)
        val = str + " ¡Saludos!";
      else
        val = "¡Saludos!";
      return val;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      string str = "¡Hola mundo!";
      Console.WriteLine("Normal: {0}", str);

      string encoded = str.Encode64();
      Console.WriteLine("Codificado: {0}", encoded);
      string encodedAscii = str.Encode64(Encoding.ASCII);
      Console.WriteLine("Codificado ASCII: {0}", encodedAscii);

      string decoded = encoded.Decode64();
      Console.WriteLine("Decodificado: {0}", decoded);
      string decodedAscii = encodedAscii.Decode64(Encoding.ASCII);
      Console.WriteLine("Decodificado ASCII: {0}", decodedAscii);

      string reversed = str.Reverse();
      Console.WriteLine("Al revés: {0}", reversed);

      Console.WriteLine("Mayúsculas: {0}", str.ToUpper());
      Console.WriteLine("Mayúsculas: {0}", TextExtensions.ToUpper(str));

      string nullstr = null;
      Console.WriteLine("Saludos normales: {0}", str.AppendGreeting());
      Console.WriteLine("Saludos desde nulo: {0}", nullstr.AppendGreeting());

      Console.ReadKey(true);
    }
  }
}

Al ejecutar este programa obtenemos la siguiente salida en la consola.

Normal: ¡Hola mundo!
Codificado: wqFIb2xhIG11bmRvIQ==
Codificado ASCII: P0hvbGEgbXVuZG8h
Decodificado: ¡Hola mundo!
Decodificado ASCII: ?Hola mundo!
Al revés: !odnum aloH¡
Mayúsculas: ¡HOLA MUNDO!
Mayúsculas: Este método nunca será invocado como extensión.
Saludos normales: ¡Hola mundo! ¡Saludos!
Saludos desde nulo: ¡Saludos!
Categorías:.NET Framework, Apunte, C# Etiquetas:
  1. Aún no hay comentarios.
  1. septiembre 29, 2010 a las 9:45 am
  2. noviembre 23, 2010 a las 5:53 pm

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s