csharp-metodos-extension

Qué son y cómo crear Métodos de Extensión en C#

  • 5 min

Un método de extensión es un método estático especial, definido en una clase estática, pero que se llama como si fuera un método de instancia del tipo que estamos extendiendo.

Imaginad que estáis trabajando con la clase String del sistema. De repente, pensáis: “Ojalá String tuviera un método .WordCount() para contar palabras directamente”.

Pero tenéis un problema: No tenéis el código fuente de String. Es una clase sellada (sealed) de .NET. No podéis heredar de ella ni modificarla.

La solución “antigua” sería crear una clase StringHelper con un método estático:

int palabras = StringHelper.WordCount(miTexto); // Funciona, pero es feo y rompe la fluidez

Copied!

La solución elegante de C# son los Métodos de Extensión. Nos permiten “inyectar” nuevos métodos a tipos existentes sin modificarlos ni heredar de ellos.

Aunque son muy potentes, tampoco os volváis locos creando extensiones para todo (“Extension Method Hell”).

Sintaxis: El modificador this

Para crear un método de extensión, necesitamos cumplir tres reglas:

  1. La clase contenedora debe ser static.
  2. El método debe ser static.
  3. El primer parámetro del método debe llevar la palabra clave this seguida del tipo que queremos extender.

Vamos a implementar el ejemplo del contador de palabras:

using System;

public static class StringExtensions
{
    // Fijaos en el 'this string str'
    public static int ContarPalabras(this string str)
    {
        if (string.IsNullOrWhiteSpace(str))
            return 0;

        return str.Split(new char[] { ' ', '.', '?' }, 
                         StringSplitOptions.RemoveEmptyEntries).Length;
    }
}
Copied!

Ahora cualquier string en nuestro proyecto (siempre que importemos el namespace) tendrá este método disponible:

string miFrase = "Hola mundo, esto es C#";

// Llamada como método de extensión (Fluida y legible)
int n = miFrase.ContarPalabras(); 
Copied!

Bajo el capó es syntactic sugar. El compilador nos hace el favor de traducir nuestra llamada bonita a la llamada estática sin que nosotros lo sepamos.

Extendiendo interfaces

Donde los métodos de extensión brillan de verdad no es extendiendo clases concretas, sino extendiendo Interfaces.

Si creas un método de extensión para una interfaz, todas las clases que implementen esa interfaz ganarán esa funcionalidad automáticamente.

Este es el secreto de LINQ. Métodos como .Where(), .Select() o .First() no están definidos en List<T> ni en Array. Son métodos de extensión sobre la interfaz IEnumerable<T>.

Ejemplo: Vamos a crear un método Print() que funcione para cualquier colección.

public static class CollectionExtensions
{
    // Extendemos IEnumerable<T>, así que sirve para List, Array, HashSet, etc.
    public static void Print<T>(this IEnumerable<T> coleccion)
    {
        Console.WriteLine("[" + string.Join(", ", coleccion) + "]");
    }
}

// Uso
int[] numeros = { 1, 2, 3 };
List<string> nombres = new List<string> { "Luis", "Ana" };

numeros.Print(); // [1, 2, 3]
nombres.Print(); // [Luis, Ana]
Copied!

Prioridad y conflictos

Es posible que os preguntéis:

¿Qué pasa si creo un método de extensión con el mismo nombre que un método que ya existe en la clase?

C# tiene una regla de prioridad: Los métodos de instancia siempre ganan.

Si la clase String tuviera un método nativo ContarPalabras(), nuestro método de extensión simplemente sería ignorado por el compilador, a menos que lo llamemos de forma estática explícita.

Tened cuidado de no “pisar” nombres futuros. Si Microsoft actualiza .NET y añade un método con el mismo nombre que vuestra extensión, vuestro código dejará de usar vuestra versión silenciosamente y pasará a usar la nativa (que podría comportarse distinto).

Manejo de nulos

A diferencia de los métodos de instancia normales, los métodos de extensión pueden ejecutarse sobre objetos null sin lanzar una excepción inmediata.

Esto ocurre porque, recordad, en realidad es una llamada estática: StringExtensions.Metodo(null).

Esto nos permite crear métodos de chequeo seguros (“Null-safe”):

public static bool IsNullOrEmpty(this string str)
{
    return string.IsNullOrEmpty(str);
}

// Uso
string texto = null;
bool vacio = texto.IsNullOrEmpty(); // ¡No explota! Devuelve true.

Copied!

Si hubiéramos llamado a texto.Length (método de instancia), habría lanzado NullReferenceException.

Ejemplos prácticos