Programación funcional en C#: de la A a la Z
La programación funcional (PF) es un paradigma que considera la computación como la evaluación de funciones matemáticas y evita el estado mutable y los efectos secundarios. A pesar de que C# es un lenguaje multiparadigma con un sólido soporte para la programación orientada a objetos (POO), sus versiones modernas (a partir de C# 3.0 en adelante) integran activamente elementos del estilo funcional. Esto permite escribir código más conciso, predecible y fácil de probar.
En este artículo, recorreremos el camino desde los fundamentos hasta las técnicas avanzadas de programación funcional en C# en el contexto de .NET. Aprenderá a usar expresiones lambda, LINQ, tipos de datos inmutables y patrones para crear aplicaciones robustas.
1. Fundamentos: Expresiones Lambda y Delegados
El corazón del enfoque funcional en C# son las expresiones lambda — funciones anónimas que se pueden usar para crear delegados o árboles de expresión. Permiten pasar comportamiento como datos.
Consideremos un ejemplo clásico de ordenación de una lista. En lugar de crear un método de comparación separado, usamos una lambda:
using System;using System.Collections.Generic;
public class LambdaExample{ public static void Main() { var numbers = new List<int> { 5, 3, 8, 1, 9 }; // Ordenación descendente con expresión lambda numbers.Sort((x, y) => y.CompareTo(x)); Console.WriteLine(string.Join(", ", numbers)); // Salida: 9, 8, 5, 3, 1 }}Conceptos clave que intervienen aquí:
- Delegados
Func<>yAction<>:Func<T, TResult>devuelve un valor,Action<T>no. Las lambdas se convierten implícitamente a estos tipos. - Clausuras (Closures): Una lambda puede capturar variables del ámbito externo, lo que la convierte en una herramienta poderosa, pero requiere cuidado debido a posibles efectos secundarios.
2. LINQ: Estilo declarativo para trabajar con datos
Language Integrated Query (LINQ) es, quizás, el ejemplo más destacado de aplicación de PF en C#. Permite escribir consultas declarativas sobre colecciones, bases de datos y XML, utilizando cadenas de métodos.
En lugar de bucles imperativos foreach, usamos operaciones funcionales:
using System;using System.Linq;using System.Collections.Generic;
public class LinqExample{ public static void Main() { var people = new List<Person> { new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 17 }, new Person { Name = "Charlie", Age = 25 } };
// Consulta funcional: filtrado, proyección, ordenación var result = people .Where(p => p.Age >= 18) // Filtrado (predicado) .OrderBy(p => p.Name) // Ordenación .Select(p => new { p.Name, IsAdult = true }) // Proyección (tipo anónimo) .ToList();
foreach (var item in result) { Console.WriteLine($"{item.Name} - Adult: {item.IsAdult}"); } // Salida: Alice - Adult: True, Charlie - Adult: True }
public class Person { public string Name { get; set; } public int Age { get; set; } }}Operadores LINQ clave:
Where— filtrado (análogo afilter).Select— transformación (análogo amap).Aggregate— reducción (análogo areduce).Any,All— comprobación de condiciones.
Es importante recordar la ejecución diferida