jueves, 22 de mayo de 2008

C# es fácil (iii): Métodos de extensión.

Bueno... vamos a seguir las entregas de C# es fácil, hablando de otras de las novedades de C# 3.0: los métodos de extensión.

En realidad los métodos de extensión no son más que la posibilidad de usar la sintaxis para llamar a un método de un objeto (vamos, el objeto.metodo() de toda la vida), para llamar a un método estático, definido en otra parte. El hecho de que el compilador nos permita usar la sintaxis objeto.metodo() nos crea la ilusión, de que el método de extensión pertenece a la clase de la cual es instancia el objeto. Pero no: el método es un simple y corriente método estático, de los de toda la vida.

Vamos a ver un ejemplo... mirad el siguiente código:

public static class Extensions


{


public static string Left(this string s, int num)


{


if (s == null) return null;


if (num <= 0) return string.Empty;


if (num <= s.Length) return s.Substring(0, num);


return s;


}


}


En él definimos una clase estática (Extensions) que contiene un método llamado Left. Fijaos en la declaración del método... en el primer parámetro lleva el modificador this. Esto lo convierte en un método de extensión. Ahora yo puedo hacer lo siguiente, desde cualquier otra parte de mi código:

string s = "Burbujas en .NET";


string s2 = s.Left(8);


Fijaos que el método que yo he definido, puedo usarlo como si fuese un método más del objeto s, que es un objeto string. Pero... que hace internamente el compilador? Pues llama al método Extensions.Left y le pasa como primer parámetro (el que tiene el this) el valor del objeto sobre el cual se ha invocado el método (en este caso mi string s).

Eso son en resumen los métodos de extensión... pero ahora unos cuantos puntos a tener claros:

  1. Los métodos de extensión NO forman parte de la clase que "extienden". Es decir, en nuestro ejemplo el método Extensions.Left, no puede acceder a los métodos protected de la clase string.

  2. Los métodos de extensión deben definirse en una clase marcada como static (y por lo tanto deben ser todos ellos static).

  3. Los métodos de extensión pueden invocarse sobre una referencia nula y no da una excepción (simplemente el método recibe null en el parámetro).

  4. Igualmente se pueden definir métodos de extensión cuyo parámetro marcado como this, sea una clase sealed. Esto nos da la ilusión de estar "extendiendo" una clase sealed.

  5. Igualmente se pueden definir métodos de extensión cuyo parámetro marcado como this sea una interface.

  6. Los métodos de extensión tambien son métodos "normales", es decir pueden llamarse en su forma... digamos tradicional. Los siguiente funcionaria:

    string s3 = Extensions.Left("Hola", 2);


  7. Los métodos de extension pueden ser genéricos, y eso vamos a verlo ahora con más detalle

Mirad el siguiente código:

public static class Extensions


{


public static string ToUpperString<T>(this T p)


{


if (p == null) return null;


return p.ToString().ToUpper();


}


}


Definimos un método de extension que funciona para cualquier objeto de cualquier tipo. Recordad que C# infiere los tipos de parámetro genéricos, o sea que las siguientes llamadas funcionan:

string s = "hola".ToUpperString();


int i = 100;


string s2 = i.ToUpperString();

En la primera llamada el compilador infiere que el tipo genérico es "string" y llama a Extensions.ToUpperString<string>(), mientras que en el segundo caso, infiere que es "int" (Int32) y llama Extensions.ToUpperString<Int32>().

En fin... eso es todo lo básico de métodos de extensión... como podeis observar no se trata más que de una treta del compilador para hacernos creer que un método está definido en una clase, cuando realmente está definido como static en otra.

Ah sí!! Y me olvidaba... ojo con el uso desmesurado de los métodos de extensión! No son y no deben ser un sustituto para la herencia o métodos normales de clase. Tienen un par de inconvenientes que hay que señalar:

  1. No pueden acceder a métodos protected de la clase que "extienden".

  2. Para poder usarlos el compilador debe poder encontrar la clase static que los contiene. Eso significa que debemos tener una referencia al assembly que contiene dicha clase static y (generalmente) el using del namespace en el cual está definida la clase.

  3. Complica el seguimiento del código por parte de terceros, porque cuando vemos objeto.metodo() no nos basta con mirar la clase (y superclases) de objeto, si no que además debemos tener presente todos los posibles métodos de extensión.

  4. Si el compilador se encuentra que puede llamar a dos métodos de extensión (vamos, que dos clases distintas me definen un método de extensión que se llama igual y ambos aceptan como parámetro this un objeto de la misma clase), el compilador se quejará con un error... exceeeeeeeeeepto si uno de los métodos de extensión es genérico, en cuyo caso el compilador a la chita callando... llamará al otro.

  5. En caso de colisión los métodos de extensión tienen menos prioridad que los métodos propios de una clase. Es decir en el siguiente código:

    public class Complex


    {

    public string ToUpperString()


    {

    return "ToUpperString de Complex";

    }


    }


    public static class Extensions

    {

    public static string ToUpperString<T>(this T p)

    {

    if (p == null) return null;

    return p.ToString().ToUpper();

    }

    }


    Una llamada a new Complex().ToUpperString(); me devolverá "ToUpperString de Complex": es decir me invocará al método propio de la clase en lugar del método de extensión.

Bueno... ahora creo que sí ya lo hemos aclarado más o menos todo... :) En resumen: usadlos con prudencia!

Saludos!

No hay comentarios: