Métodos de extensión en C#

“Los métodos de extensión permiten “agregar” métodos a los tipos existentes sin necesidad de crear un nuevo tipo derivado y volver a compilar o sin necesidad de modificar el tipo original. Los métodos de extensión constituyen un tipo especial de método estático, pero se les llama como si se tratasen de métodos de instancia en el tipo extendido. En el caso del código de cliente escrito en C# y Visual Basic, no existe ninguna diferencia aparente entre llamar a un método de extensión y llamar a los métodos realmente definidos en un tipo.” MSDN

Ciertamente esta es una descripción bastante criptica de esta técnica de desarrollo que se puede utilizar para una mayor productividad y legibilidad de nuestro código. Por ello quiero escribir un artículo que, por medio de un ejemplo muy sencillo, pueda ser un paso inicial en el conocimiento de esta herramienta de programación.

Básicamente, y para entendernos de una forma más sencilla. Un método extensor es una forma de “añadirle“ métodos a una clase sin necesidad de hacerlo en la original o en alguna instancia/copia de la misma. No es un reemplazo de la técnica formal por medio de herencia, si no una pequeña “trampa” que nos permite obtener un resultado muy similar.


Preparando el proyecto


Actualmente estoy utilizando como editor principal el Visual Studio 11 Beta, que no cuesta ni un real (pero lo costará). Pero si quisiera un producto gratuito para programar en C# podría también utilizar el Visual Studio Express 11.

Para este artículo me creo un nuevo proyecto Web, de tipo MVC4 (que también es beta) vacío. He elegido este tipo para no tener ningún tipo de ruido en el ejemplo que quiero compartir. Por ello me voy a saltar la regla de hacer siempre test unitarios, y no voy a construir el proyecto de testing.

A continuación añado una página de clases en la carpeta de Modelo, llamada Comprobaciones, en donde voy a insertar una clase que realice diversas comprobaciones básicas y en donde voy a definir métodos extensores que me haga el código más legible.

El primer paso de mi ejemplo es enunciarlo. Describir la visión de lo que quiero hacer. Y esto es realizar una clase que me compruebe si los datos de un objeto Persona son correctos. La Persona va a tener dos propiedades: el nombre y la edad en años. Y por ello lo primero que voy a hacer es construir la estructura del objeto.

 public class Persona
 {
     public string Nombre { get; set; }
     public int Edad { get; set; }
 }

Ahora continuo creando una clase de comprobaciones en donde generaré el método de entrada que se llamará esCorrecto, recibiendo como parámetro un objeto del tipo Persona. Como verás, la primera validación que debo realizar es que persona no sea un objeto nulo. Para ello hago algo tan simple como retornar si el objeto no es nulo.
  
    public class Comprobaciones
    {
        public bool esCorrecto(Persona persona)
        {
            return persona != null;
        }
    }

Extendiendo a Persona


Pero mejoremos la legibilidad del código refactorizando con métodos extensores y creemos un nuevo método para la clase Persona. Esto se hace de forma muy sencilla creando una nueva clase estática llamada extensores en donde voy a dar de alta un método también estático que tiene la particularidad de extender la clase Persona de la siguiente forma.
 
public static class Extensores
    {
        public static Boolean IsNotNull(this Persona persona)
        {
            return persona != null;
        }
    }

Como ves en este ejemplo tan sencillo ganas un poquito de complejidad para ganar un poquito de legibilidad, quedando nuestro método de comprobaciones así.

    public class Comprobaciones
    {
        public bool esCorrecto(Persona persona)
        {
            return persona.IsNotNull();
        }
    }

Pero si lo piensas un poco, el saber si una clase es nula o no me puede venir muy bien en toda la aplicación en donde tendré muchos más objetos a los que hacer la misma comprobación. O sea que con muy poco esfuerzo puedo extender el propio tipo object y así TODOS mis objetos serán capaces de devolverme si son nulos o no. Incluyendo todas las clases, estructuras, enumeradores, etc. Ya que en C# todo hereda de object.

        public static Boolean IsNotNull(this Object objeto)
        {
            return objeto != null;
        }

Así, con un solo método extensor puedo comprobar si la edad o el nombre son nulos de una forma, no solamente sencilla si no muy legible.

    public class Comprobaciones
    {
        public bool esCorrecto(Persona persona)
        {
            return persona.Edad.IsNotNull();
        }
    }

Pero vamos a darle una pequeña vuelta de tuerca extendiendo otra vez a la clase Persona para realizar la comprobación de que tiene un nombre válido. Como todos sabemos, en la última versión del framework de .NET podemos hacer dos comprobaciones diferenciadas que nos digan si la cadena de texto es nula, esta vacía o es un espacio en blanco. Por lo cual si quiero comprobar si el nombre es válido debo hacer dos llamadas diferenciadas.
 
    public class Comprobaciones
    {
        public bool esCorrecto(Persona persona)
        {
            return string.IsNullOrEmpty(persona.Nombre) == false 
                   && string.IsNullOrWhiteSpace(persona.Nombre);
        }
    }

Pero si utilizo un método extensor tal cual este,
 
        public static Boolean NoTieneNombre(this Persona persona)
        {
            return string.IsNullOrEmpty(persona.Nombre) 
                   && string.IsNullOrWhiteSpace(persona.Nombre);
        }

Voala, la legibilidad de mi código aumenta mucho y además tengo encapsulado en un solo sitio la lógica de comprobación del campo. Es más, así cumplo a rajatabla el concepto de que mi método no debe saber cómo se hacen las cosas, solamente esperar la respuesta adecuada; en este caso un valor booleano.
 
    public class Comprobaciones
    {
        public bool esCorrecto(Persona persona)
        {
            return persona.NoTieneNombre();
        }
    }

Conclusiones


Con esta técnica podemos obtener un código más legible y mantenible. Pero no es todo oro lo que reluce y hay que tener los inconvenientes inherentes a esta técnica.

Primero el código es dependiente del intellisense del Visual Studio, ya que desde el fichero fuente abierto no puedes saber si el método invocado es parte de la instancia o es un método extensor. Lo cual puede causar dolores de cabeza para localizar el código si no estás con el IDE adecuado.

Otro problema es que el ciclo de vida de la clase y de los métodos que la extiende pueden ser totalmente diferentes. Por lo cual un extensor que funcione bien en un momento, por un cambio en el objeto que extiende puede dejar de hacerlo correctamente.

Por último si se realiza un cambio en la clase extendida y se le añade un método de instancia con el mismo nombre que el método extensor, este dejará de funcionar ya que el compilador utilizará antes el método de instancia que el extensor. Lo cual es un error muy difícil de localizar.

A causa de ello, se aconseja darle prioridad al uso de los métodos de instancia y a la herencia, antes que a los métodos extensores. Lo cual no quita que herramientas como Linq esté construida en su totalidad con esta técnica de desarrollo.

Ahora solo queda, probar y practicar, para tener un conocimiento en más profundidad que te permita tomar las decisiones más productivas.


namespace GenbetaDevMVC4.Models
{ public class Persona { public string Nombre { get; set; } public int Edad { get; set; } }

public class Comprobaciones
{
    public bool esCorrecto(Persona persona)
    {
        return ! persona.NoTieneNombre();
    }
}

public static class Extensores
{
    public static Boolean IsNotNull(this Object objeto)
    {
        return objeto != null;
    }
    public static Boolean NoTieneNombre(this Persona persona)
    {
        return string.IsNullOrEmpty(persona.Nombre) 
               && string.IsNullOrWhiteSpace(persona.Nombre);
    }
}

}

03/05/2012 He añadido un enlace a una biblioteca de métodos extensores que será de gran utilidad.

Más información | Métodos de extensión (Guía de programación de C#), Extension Method

Portada de Genbeta