Cuando deseamos añadir un nuevo método a una clase existente, típicamente editamos el código fuente de la misma, si disponemos de acceso al mismo, o creamos una clase derivada y añadimos en ella el nuevo método. Cuando esto no es factible por alguna razón (por ejemplo, si es una clase de tipo sealed), podemos recurrir a los métodos de extensión.
A la hora de definirlos, los métodos de extensión tienen el mismo aspecto que los métodos estáticos, con la diferencia de que su primer argumento lleva antepuesta la palabra clave this (que en este contexto no tiene nada que ver con el this que habitualmente utilizamos para tomar una referencia a la instancia actual).
A la hora de llamar al método, no escribimos ese argumento. En su lugar, el compilador toma la instancia del objeto sobre la que estamos llamando al método. Por ejemplo:
public static class Extensores
{
public static int ContarPalabras(this string texto)
{
return texto.Split(' ').Length;
}
}
Podemos ver que se trata de un método estático definido dentro de una clase estática. Para que funcione el método de extensión es obligatorio que tanto el método como la clase sean static.
Para llamar al método, lo invocamos desde cualquier cadena, como si el método formase parte de la clase string (el tipo de argumento que va detrás del this en la declaración):
static void Main(string[] args)
{
string ejemplo = "Esta frase tiene cinco palabras.";
int n = ejemplo.ContarPalabras();
Console.WriteLine("Hay {0} palabras.", n);
}
La llamada a ContarPalabras se realiza sobre la cadena ejemplo de la misma manera que si fuera alguno de los métodos estándar de la clase string . Desde el punto de vista del código llamante, no se nota ninguna diferencia respecto a los métodos nativos de la clase.
Al teclear el código en Visual Studio, intellisense muestra el método de extensión en la misma lista que el resto de los métodos, pero se ilustra con un icono ligeramente distinto para que podamos reconocer visualmente que se trata de un método de extensión.
En la anterior imagen vemos varios métodos de extensión (los que tienen una flecha azul), además del método ContarPalabras que nosotros hemos escrito. Estos métodos provienen de las librerías de System.Linq, que estaban referenciadas mediante un using al principio del programa del que se copió el ejemplo de la figura.
No solo se pueden generar métodos de extensión sobre una clase, sino también sobre una interfaz. De esta manera, todas las demás clases que hereden de esa clase, o que implementen esa interfaz, recibirán el método de extensión.
Los métodos de extensión también pueden ser genéricos. Por ejemplo, en System.Linq hay varios métodos de extensión que extienden la interfaz IEnumerable<T> de una forma similar a esta:
public static string Combinar<T>(this IEnumerable<T> ien)
{
StringBuilder sb = new StringBuilder();
foreach (T item in ien)
{
sb.Append(item.ToString());
}
return sb.ToString();
}
Después de definir este método, se puede invocar sobre cualquier objeto que implemente la interfaz IEnumerable<T>, por ejemplo, sobre un List<int>, que implementa IEnumerable<int>:
List<int> lista = new List<int>{ 9,8,7,6 };
Console.WriteLine(lista.Combinar());
Si se desea, se pueden añadir argumentos adicionales en los métodos de extensión. Después, en la llamada al método, esos argumentos se indican con normalidad entre los paréntesis, como si se hubiese declarado el método sin el argumento “this”.
public static int ContarPalabras(this string texto, char separador)
{
return texto.Split(separador).Length;
}
string ejemplo = "Hola, mundo. ¿Qué tal?";
int n = ejemplo.ContarPalabras(',');
Console.WriteLine(n); //Escribe "2"
Es sencillo añadir métodos de extensión en nuestros programas, pero se recomienda no abusar de este mecanismo ya que puede ocasionar una pérdida de claridad y mantenibilidad del código, al aparecer en las clases métodos que no figuran dentro del código fuente de las mismas.
Una aplicación común de los métodos de extensión consiste en extender interfaces tales como IEnumerable definiéndoles métodos tales como Where, Select u OrderBy, que luego permiten utilizar sentencias LINQ sobre cualquier objeto que implemente la interfaz en cuestión.