Menu

Métodos de extensión en C#: sintaxis, IEnumerable, LINQ y reglas

Cómo funcionan los métodos de extensión en C#: un método static en una clase static con this en el primer parámetro, llamarlo como un método de instancia, extender string, enums e IEnumerable<T>, cómo se construye LINQ con ellos, por qué ganan los métodos de instancia, receptores null y la directiva using que necesitan.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un método de extensión añade un método a un tipo existente sin modificarlo, heredar de él ni envolverlo. Escribes un método static normal, marcas su primer parámetro con this, y quien llama lo usa con la sintaxis de punto como si el tipo siempre lo hubiera tenido:

Salida:

Extension methods let yo...
12
short
Extension...

title.Truncate(24) se compila exactamente como StringExtensions.Truncate(title, 24). La sintaxis de punto es solo una comodidad: el método sigue siendo static, sigue viviendo en StringExtensions y solo puede usar los miembros públicos de string.

Las reglas para declarar uno

  • El método debe ser static y vivir en una clase static, no genérica y no anidada dentro de otra clase. Una clase no static o genérica da CS1106, Extension method must be defined in a non-generic static class; una anidada da CS1109, Extension methods must be defined in a top level static class.
  • this va solo en el primer parámetro, y el tipo de ese parámetro es el tipo que se extiende. Los demás parámetros son normales.
  • El método puede ser genérico él mismo (this IEnumerable<T> source), aunque la clase no pueda serlo.
  • Solo ve lo que ve cualquier código externo: los miembros públicos (y, dentro del mismo ensamblado, los internal). Los campos privados siguen siendo privados.

La convención es nombrar la clase según lo que extiende (StringExtensions, EnumerableExtensions) y agruparlas en un namespace como MyApp.Extensions.

Extender interfaces e IEnumerable<T>

Extender una interfaz añade el método a todos los tipos que la implementan. Por eso, extender IEnumerable<T> da el método a la vez a los arrays, las listas, los conjuntos, las colecciones de claves de los diccionarios y los resultados de las consultas LINQ:

Salida:

9.5
50
65
f0 f3 f6

EveryNth es genérico: T se deduce del receptor, así que funciona aquí con strings y con cualquier otro tipo de elemento. Como usa yield return, es perezoso como los operadores LINQ incorporados y puede encadenarse con ellos.

LINQ está construido sobre métodos de extensión

Where, Select, OrderBy, Sum, First y el resto de LINQ son métodos de extensión sobre IEnumerable<T>, definidos en la clase static System.Linq.Enumerable. Por eso using System.Linq; tiene que estar al principio de un archivo para que list.Where(...) compile, y por eso LINQ funciona con todos los tipos de colección sin que ninguno implemente un método Where:

Salida:

72, 88, 95
72, 88, 95

Las dos líneas son el mismo código. La sintaxis de extensión convierte llamadas anidadas, que se leen de dentro hacia fuera, en una cadena que se lee en el orden en que ocurren los pasos, y esa es la razón principal por la que existen los métodos de extensión: se añadieron en C# 3 junto con LINQ. La página de LINQ explica los operadores.

Hay que importar el namespace

Un método de extensión solo está en ámbito cuando se importa con using el namespace de su clase static. Sin él, la llamada falla como si el método no existiera:

error CS1061: 'string' does not contain a definition for 'Truncate' and no accessible extension method 'Truncate' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)

Añade el using del namespace de las extensiones (o, desde C# 10, un global using una vez para todo el proyecto):

Salida:

EUR 49.90

La otra cara es útil: un método de extensión que no quieres en todas partes puede vivir en un namespace que solo importan algunos archivos.

Ganan los métodos de instancia

Cuando un tipo ya tiene un método de instancia aplicable, el compilador lo usa y nunca tiene en cuenta la extensión. Una extensión con la misma firma es código muerto en todos los puntos de llamada que usan la sintaxis de punto:

Salida:

Invoice (instance method): 120
Invoice (extension): 120 USD
extension: only reachable as a static call

La extensión de un argumento perdió frente al método de instancia; la de dos argumentos se usó porque ningún método de instancia recibe un string. Esto también significa que una actualización de una biblioteca que añada un método de instancia con el nombre de tu extensión cambia en silencio tus llamadas a la versión de la biblioteca. Elige nombres de extensión con pocas probabilidades de chocar.

Llamar sobre null

Como la llamada es en realidad una llamada static, un receptor null no lanza una excepción en el punto de llamada. El método recibe null como primer argumento y decide qué significa:

Salida:

True
AL
ArgumentNullException: fullName

Que missing.IsBlank() devuelva true es cómodo, pero puede sorprender a los lectores que esperan que una llamada a un miembro sobre null lance una excepción. Acepta null solo en métodos cuyo nombre lo haga evidente (IsBlank, OrEmpty); en los demás, lanza ArgumentNullException como hace LINQ.

Extender enums

Los enums no pueden tener métodos propios, lo que los convierte en un objetivo natural:

enum OrderStatus { Pending, Paid, Shipped, Delivered, Cancelled }

static class OrderStatusExtensions
{
    public static bool IsFinal(this OrderStatus s) =>
        s == OrderStatus.Delivered || s == OrderStatus.Cancelled;
}

// usage: if (order.Status.IsFinal()) { ... }

Miembros de extensión en C# 14

Hasta C# 14, solo los métodos podían ser extensiones. C# 14 añadió los bloques extension, que agrupan miembros para un receptor y permiten también propiedades de extensión y miembros static:

// C# 14 and later
public static class StringExtensions
{
    extension(string s)
    {
        public bool IsBlank => string.IsNullOrWhiteSpace(s);   // extension property
        public string Truncate(int max) => s.Length <= max ? s : s[..max] + "...";
    }
}

Los métodos de extensión clásicos con el parámetro this siguen siendo válidos y son lo que usan casi todo el código y las bibliotecas existentes.

Cuándo escribir uno

Los métodos de extensión encajan cuando el tipo no es tuyo (string, DateTime, interfaces del framework), cuando quieres el mismo método auxiliar en todas las implementaciones de una interfaz, o cuando una cadena se lee mejor que unas llamadas anidadas. Cuando la clase es tuya, añade un método de instancia de verdad: puede usar el estado privado y aparece donde los lectores lo buscan. Y evita extender object, que pone tu método en la lista de autocompletado de todos los valores del programa.

Preguntas frecuentes

¿Qué es un método de extensión en C#?

Un método static que puede llamarse como si fuera un método de instancia de otro tipo. Lo escribes en una clase static y pones this delante de su primer parámetro: public static bool IsBlank(this string s) => string.IsNullOrWhiteSpace(s);. Así name.IsBlank() funciona con cualquier string, sin cambiar string ni heredar de él.

¿Por qué no se encuentra mi método de extensión?

Los métodos de extensión solo son visibles cuando se importa su namespace. Si la clase static está en MyApp.Extensions, añade using MyApp.Extensions; al archivo que lo llama; si no, el compilador da CS1061, diciendo que el tipo no contiene una definición ni un método de extensión accesible con ese nombre. Comprueba también que la clase sea static, no genérica y no anidada.

¿Puede un método de extensión sustituir a un método de instancia?

No. El compilador busca primero un método de instancia aplicable y solo tiene en cuenta los métodos de extensión cuando no existe ninguno. Una extensión con el mismo nombre y los mismos parámetros que un método de instancia se ignora en silencio en todos los puntos de llamada que usan la sintaxis de instancia.

¿Se puede llamar a un método de extensión sobre null en C#?

Sí. La llamada se compila como una llamada a un método static con null como primer argumento, así que la propia llamada no lanza NullReferenceException. El método decide qué hacer: tratar null como una entrada válida (como un método auxiliar al estilo de IsNullOrEmpty) o lanzar ArgumentNullException, como hacen los métodos de LINQ.

¿Existen propiedades de extensión en C#?

No antes de C# 14. Hasta entonces solo los métodos pueden ser extensiones, así que lo que sería una propiedad se escribe como un método (GetFullName()). C# 14 añadió los bloques de extensión, que pueden declarar propiedades de extensión y miembros static además de métodos.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR