Programmation fonctionnelle en C# : de la théorie à la pratique
La programmation fonctionnelle (PF) est un paradigme dans lequel les calculs sont considérés comme l'évaluation de fonctions mathématiques, en évitant les changements d'état et les données mutables. Bien que C# soit à l'origine un langage orienté objet, depuis la version 3.0, il intègre activement des fonctionnalités fonctionnelles. Aujourd'hui, C# prend en charge des concepts tels que les expressions lambda, LINQ, les types immuables et même le pattern matching. Dans cet article, nous analyserons les principes clés de la PF et montrerons comment les appliquer dans des projets réels en C#.
1. Fonctions pures et absence d'effets secondaires
Une fonction pure est une fonction qui :
- Retourne toujours le même résultat pour les mêmes arguments (transparence référentielle).
- N'a pas d'effets secondaires (ne modifie pas l'état global, n'écrit pas dans des fichiers, n'envoie pas de requêtes HTTP).
En C#, une fonction pure peut ressembler à ceci :
public static int Add(int a, int b) => a + b;Voici un exemple de fonction impure :
public static int AddAndLog(int a, int b){ Console.WriteLine($"Addition de {a} et {b}"); // effet secondaire return a + b;}Avantages des fonctions pures : elles sont faciles à tester, à déboguer et à paralléliser.
2. Immuabilité (Immutability)
En PF, les données sont par défaut immuables. En C#, on utilise pour cela :
- Le mot-clé
readonlypour les champs. - Les structures
record(C# 9+), qui créent automatiquement des objets immuables. - Les collections de l'espace de noms
System.Collections.Immutable.
Exemple avec record :
public record Person(string Name, int Age);Si l'on doit modifier l'âge, une nouvelle instance est créée :
Person person = new("Ivan", 30);Person olderPerson = person with { Age = 31 };L'immuabilité empêche les mutations accidentelles et simplifie le multithreading.
3. Fonctions d'ordre supérieur et expressions lambda
Les fonctions d'ordre supérieur prennent d'autres fonctions comme arguments ou les retournent. En C#, cela est implémenté via les délégués : Func<>, Action<>, Predicate<>.
Un exemple simple — la méthode Where de LINQ :
List<int> numbers = new() { 1, 2, 3, 4, 5 };var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();Ici, n => n % 2 == 0 est une expression lambda (fonction anonyme).
On peut écrire sa propre fonction d'ordre supérieur :
public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate){ var result = new List<T>(); foreach (var item in list) if (predicate(item)) result.Add(item); return result;}4. LINQ : un outil puissant de style fonctionnel
LINQ (Language Integrated Query) est sans doute l'exemple le plus frappant de programmation fonctionnelle en C#. Il permet de traiter des collections de manière déclarative, comme en SQL.
Exemple : obtenir les noms des utilisateurs de plus de 18 ans, triés par âge :
var users = new List<User>{ new("Anna", 22), new("Boris", 17), new("Victor", 30)};
var result = users .Where(u => u.Age >= 18) .OrderBy(u => u.Age) .Select(u => u.Name) .ToList();LINQ prend en charge l'exécution différée (lazy evaluation), ce qui économise des ressources.
5. Monades et type Maybe en C#
Les monades sont une abstraction pour les chaînes de calculs avec contexte (par exemple, la gestion de null ou l'asynchronisme). En C#, il n'y a pas de monade Maybe intégrée, mais il est facile de l'implémenter :
public class Maybe<T>{ private readonly T _value; public bool HasValue { get; } p