CodeGym /Cursos /C# SELF /Funciones de orden superior

Funciones de orden superior

C# SELF
Nivel 51 , Lección 2
Disponible

1. Introducción

Seguro que en la escuela te dijeron: "Esto es una función de una función". Las funciones de orden superior (Higher-Order Functions, HOF) son funciones que o bien aceptan funciones como argumentos, o bien devuelven funciones como resultado, o ambas cosas.

Dicho más sencillo: si tu método puede recibir como parámetro otra función (por ejemplo, un delegado o una lambda), o devolverla como resultado — ¡felicidades, tienes una función de orden superior!

Una función de orden superior es como una llave maestra que no solo puede abrir una puerta por sí misma, sino también darte otra llave para que abras la puerta que necesites más tarde.

Aplicación en la vida real

Todo esto suena interesante y no trivial. Pero, ¿para qué sirven estos malabares en el día a día de un desarrollador en C#? Aquí tienes la respuesta corta:
Las funciones de orden superior hacen el código flexible, reutilizable y conciso.
Son la base de cosas como LINQ, filtrado y ordenación de colecciones, construcción de pipelines de procesamiento de datos, configuración de callbacks, eventos e incluso inyección de dependencias.

Algunos escenarios reales:

  • Limitar el comportamiento de un método pasando la lógica dentro (por ejemplo, filtrado, ordenación, transformación).
  • Escribir un manejador de datos genérico al que "le atornillas" la operación necesaria mediante un delegado.
  • Construir cadenas de procesamiento ("pipelines"), donde cada función modifica los datos según su receta.
  • Crear abstracciones multiplataforma: ¿qué hacer en Windows, qué en Linux? Simplemente pasa la función adecuada.

2. Ejemplos de funciones de orden superior sencillas

Función que recibe otra función

El ejemplo más clásico — un método que acepta un delegado o una lambda.


// Función de orden superior: recibe la función process como parámetro
void ForEach<T>(IEnumerable<T> collection, Action<T> process)
{
    foreach (var item in collection)
    {
        process(item); // Llamamos a la función-argumento
    }
}

// Uso:
var numbers = new List<int> { 1, 2, 3 };
ForEach(numbers, n => Console.WriteLine($"Elemento: {n}"));

¿Qué pasa aquí? El método ForEach no sabe qué hacer exactamente con cada elemento. Todo lo que hace es invocar al procesador que le pasaron (process). Ese procesador puede ser cualquier cosa: imprimir en pantalla, guardar en la base, dibujar en pantalla, etc.

Sí, así funciona el método ForEach en las colecciones de C#, ¡y prácticamente todos los métodos de LINQ son funciones de orden superior!

Función que devuelve otra función

Ahora un ejemplo más "pesado": un método que no solo acepta, sino que también devuelve una función.


// Función de orden superior: devuelve otra función
Func<int, int> CreateMultiplier(int factor)
{
    // Devolvemos una lambda que usa la variable factor
    return x => x * factor;
}

// Uso:
var multiplyBy10 = CreateMultiplier(10);
Console.WriteLine(multiplyBy10(7)); // 70

Aquí CreateMultiplier devuelve una función que multiplica su argumento por un factor preestablecido. Esto ya no es solo una HOF, es un ejemplo de "fábrica de funciones".

Función que recibe y devuelve una función


Func<int, int> Compose(Func<int, int> f, Func<int, int> g)
{
    // Devolverá una función que aplica g, y luego f: f(g(x))
    return x => f(g(x));
}

// Uso:
Func<int, int> increment = x => x + 1;
Func<int, int> doubleIt = x => x * 2;

var incrementThenDouble = Compose(doubleIt, increment);

Console.WriteLine(incrementThenDouble(5)); // (5 + 1) * 2 = 12

Precisamente estas composiciones están detrás del procesamiento por etapas — por ejemplo, cuando tratas un array con Select, Where, OrderBy, etc.

3. Cómo C# soporta funciones de orden superior

En lenguajes funcionales (Haskell, F#) todas las funciones son de orden superior por defecto. Pero C# (desde la versión 2.0) también soporta este enfoque gracias a los delegados y las lambdas.

  • Delegados (Func, Action, Predicate) — tipos que representan funciones.
  • Expresiones lambda — sintaxis para crear funciones en el sitio.
  • Los métodos pueden recibir y devolver delegados — así que las funciones de orden superior están soportadas "de serie".

Esquema visual

flowchart LR
    A[Datos] --> B[Función 1]
    B --> C[Función 2]
    C --> D[Resultado]
    subgraph "Pipeline de procesamiento (funciones de orden superior)"
        B
        C
    end

4. Desarrollamos nuestra aplicación

Seguimos desarrollando nuestro demo paso a paso — que sea un "Mini-procesador de cadenas".

Añadimos un método de orden superior sencillo

Imagina que tenemos una lista de nombres de usuario y queremos modificarlos arbitrariamente usando funciones.


// Método que recibe una lista de strings y una función para transformar
List<string> TransformNames(List<string> names, Func<string, string> transformer)
{
    var result = new List<string>();
    foreach (var name in names)
    {
        result.Add(transformer(name));
    }
    return result;
}

¿Cómo usar este método?


var names = new List<string> { "Anna", "Boris", "Sergey" };

// Convertir a mayúsculas
var upperNames = TransformNames(names, n => n.ToUpper());

// Añadir "Estimado(a)" a cada nombre
var politeNames = TransformNames(names, n => "Estimado(a) " + n);

foreach (var n in upperNames)
    Console.WriteLine(n); // ANNA, BORIS, SERGEY

foreach (var n in politeNames)
    Console.WriteLine(n); // Estimado(a) Anna, ...

El método TransformNames es universal: delega la lógica de transformación al parámetro (transformer), por ejemplo a la llamada ToUpper o a cualquier otra "receta".

Adaptación a tipos

Nuestro ejemplo se adapta fácilmente a cualquier tipo de dato.


// Método genérico - función de orden superior que trabaja con cualquier T
List<TResult> Map<T, TResult>(List<T> items, Func<T, TResult> transformer)
{
    var result = new List<TResult>();
    foreach (var item in items)
    {
        result.Add(transformer(item));
    }
    return result;
}

Ejemplo de uso:


var numbers = new List<int> { 1, 2, 3 };
var doubled = Map(numbers, x => x * 2); // [2, 4, 6]
var strings = Map(numbers, x => $"Número: {x}"); // ["Número: 1", ...]

5. Filtrado y agregación mediante funciones superiores

La lógica de filtrado y búsqueda se implementa desde hace tiempo mediante funciones de orden superior.


// Filtrado: función de orden superior
List<T> Filter<T>(List<T> items, Predicate<T> criteria)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (criteria(item)) // Llamamos a la función-criterio
        {
            result.Add(item);
        }
    }
    return result;
}

Cómo usarlo:


var names = new List<string> { "Anna", "Boris", "Andrey" };
var aNames = Filter(names, n => n.StartsWith("A"));
// Resultado: "Anna", "Andrey"

6. Concepto de composición de funciones (function composition)

Las funciones de orden superior permiten no solo usar funciones individuales, sino también ensamblarlas en cadenas — las llamadas composiciones. En C# puedes implementarlo como una función que recibe dos funciones y devuelve una nueva que las combina.


// Compositor de funciones: devuelve una función que aplica primero g y luego f
Func<T, TResult> Compose<T, TIntermediate, TResult>(
    Func<TIntermediate, TResult> f,
    Func<T, TIntermediate> g)
{
    return x => f(g(x));
}

// Ejemplo:
Func<int, int> plusOne = n => n + 1;
Func<int, int> timesTwo = n => n * 2;

var plusOneThenDouble = Compose(timesTwo, plusOne);
Console.WriteLine(plusOneThenDouble(3)); // (3 + 1) * 2 = 8

7. Matices útiles

Historia del problema: ¿por qué antes todo era más complicado?

Antes de delegados y lambdas, los desarrolladores solían escribir muchos bucles similares y copiar trozos de código para "filtrar", "transformar" o "agrupar" datos. Con la llegada de las funciones de orden superior apareció la posibilidad de extraer la parte variable del comportamiento en funciones-parámetro independientes — reduciendo radicalmente la duplicación y aumentando la expresividad del código.

Un poco de azúcar sintáctico: funciones como expresiones

Las funciones de orden superior a menudo se implementan con métodos expression-bodied — métodos de una sola línea y concisos:


List<string> FilterNames(Predicate<string> pred) =>
    Names.Where(name => pred(name)).ToList();

List<TResult> MapNames<TResult>(Func<string, TResult> transformer) =>
    Names.Select(transformer).ToList();

Comparación con los mecanismos de LINQ

Veamos cómo LINQ utiliza funciones de orden superior:

Método de LINQ Qué delegado acepta Propósito
Where
Func<T, bool>
Filtra elementos
Select
Func<T, TResult>
Transforma elementos
OrderBy
Func<T, TKey>
Ordena por clave
Aggregate
Func<TAcc, T, TAcc>
Agrega (reduce) la colección
Any
Func<T, bool>
Comprueba si existe un elemento que cumple la condición

Todos estos métodos se construyen alrededor de la idea de funciones de orden superior: tú escribes tus reglas, y la biblioteca estándar proporciona la "infraestructura".

8. Posibles errores y trampas

Confusión con los tipos de delegados.
Al principio puede ser difícil entender cuándo usar Action, cuándo Func y cuándo Predicate.
Pista: si la función devuelve bool — probablemente sea un Predicate. Si devuelve un valor — usa Func, si no devuelve nada — Action.

Captura de variables (closures).
Si la función devuelta usa variables del entorno externo, fíjate en que los valores sean los correctos en el momento de la llamada. Las variables no se copian, se "capturan".

Depurar cadenas complejas.
Cuando las funciones se componen en pipelines largos, puede ser difícil detectar qué capa procesó mal los datos. Añade salidas temporales y comentarios:


n => {
  Console.WriteLine("Antes de la operación:" + n);
  var res = n * 2;
  Console.WriteLine("Después:" + res);
  return res;
}
2
Tarea
C# SELF, nivel 51, lección 2
Bloqueada
Filtrado usando funciones de orden superior
Filtrado usando funciones de orden superior
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION