CodeGym /Cours /C# SELF /Fonctions d'ordre supérieur

Fonctions d'ordre supérieur

C# SELF
Niveau 51, Leçon 2
Disponible

1. Introduction

On a sûrement entendu à l'école : « C'est une fonction d'une fonction ». Les fonctions d'ordre supérieur (Higher-Order Functions, HOF) — ce sont des fonctions qui prennent des fonctions comme arguments, ou qui retournent des fonctions comme résultat, ou les deux à la fois.

En termes simples : si ta méthode peut recevoir une autre fonction en paramètre (par ex. un delegate ou une lambda), ou la renvoyer — félicitations, tu as une fonction d'ordre supérieur !

Une fonction d'ordre supérieur, c'est comme une clé passe-partout : elle peut non seulement ouvrir une porte elle-même, mais aussi te donner une autre clé pour ouvrir une porte plus tard.

Applications dans la vraie vie

Tout ça sonne bien et un peu abstrait. Mais à quoi ça sert vraiment dans le quotidien d'un dev C# ? La réponse courte :
Les fonctions d'ordre supérieur rendent le code flexible, réutilisable et concis.
C'est la base de trucs comme LINQ, le filtrage et le tri de collections, la construction de pipelines de traitement, la configuration de callbacks, d'événements, et même l'injection de dépendances.

Quelques scénarios concrets :

  • Limiter le comportement d'une méthode en lui passant la logique (par ex. filtrage, tri, transformation).
  • Écrire un handler de données générique dans lequel tu "visses" l'opération voulue via un delegate.
  • Construire des chaînes de traitement ("pipelines") où chaque fonction modifie les données selon sa recette.
  • Créer des abstractions cross-platform : que faire sous Windows, que faire sous Linux ? Il suffit de passer la bonne fonction.

2. Exemples simples de fonctions d'ordre supérieur

Fonction qui prend une autre fonction

Le classique — une méthode qui prend un delegate ou une lambda en paramètre.


// Fonction d'ordre supérieur : prend la fonction process en paramètre
void ForEach<T>(IEnumerable<T> collection, Action<T> process)
{
    foreach (var item in collection)
    {
        process(item); // On appelle la fonction passée en argument
    }
}

// Utilisation :
var numbers = new List<int> { 1, 2, 3 };
ForEach(numbers, n => Console.WriteLine($"Élément : {n}"));

Que se passe-t-il ici ? La méthode ForEach ne sait pas quoi faire avec chaque élément. Tout ce qu'elle fait — c'est appeler le processeur passé (process). Ce processeur peut faire n'importe quoi — afficher, sauvegarder en base, dessiner, etc.

Oui, c'est comme ça que marche la méthode ForEach sur les collections en C#, et presque toutes les méthodes LINQ — ce sont des fonctions d'ordre supérieur !

Fonction qui retourne une fonction

Maintenant un exemple un peu plus costaud — une méthode qui retourne une fonction.


// Fonction d'ordre supérieur : retourne une autre fonction
Func<int, int> CreateMultiplier(int factor)
{
    // On retourne une lambda utilisant la variable factor
    return x => x * factor;
}

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

Ici CreateMultiplier renvoie une fonction qui multiplie son argument par un facteur donné. C'est une sorte de "factory" de fonctions.

Fonction qui prend et retourne une fonction


Func<int, int> Compose(Func<int, int> f, Func<int, int> g)
{
    // Retourne une fonction qui applique g puis f : f(g(x))
    return x => f(g(x));
}

// Utilisation :
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

Ce sont exactement ces compositions qui sont au cœur du traitement de données en chaîne — par exemple quand tu enchaînes Select, Where, OrderBy, etc.

3. Comment C# supporte les fonctions d'ordre supérieur

Dans les langages fonctionnels (Haskell, F#) toutes les fonctions sont d'ordre supérieur par défaut. Mais C# (depuis la version 2.0) supporte aussi ce style grâce aux delegates et aux lambdas.

  • Delegates (Func, Action, Predicate) — types de fonctions.
  • Expressions lambda — syntaxe pour créer des fonctions sur place.
  • Les méthodes peuvent accepter et retourner des delegates — donc les HOFs sont supportées "out of the box".

Schéma visuel

flowchart LR
    A[Données] --> B[Fonction 1]
    B --> C[Fonction 2]
    C --> D[Résultat]
    subgraph "Pipeline de traitement (fonctions d'ordre supérieur)"
        B
        C
    end

4. On enrichit notre appli

Continuons à faire évoluer notre demo pas à pas — imaginons un "Mini-transformateur de chaînes".

Ajout d'une méthode HOF simple

Supposons qu'on a une liste de noms d'utilisateurs et qu'on veut les modifier arbitrairement avec des fonctions.


// Méthode qui prend une liste de strings et une fonction de transformation
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;
}

Comment utiliser cette méthode ?


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

// Transformer en majuscules
var upperNames = TransformNames(names, n => n.ToUpper());

// Préfixer par "Cher/Chère " pour chaque nom
var politeNames = TransformNames(names, n => "Cher/Chère " + n);

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

foreach (var n in politeNames)
    Console.WriteLine(n); // Cher/Chère Anna, ...

La méthode TransformNames est générique : elle délègue la logique de transformation au paramètre (transformer), que ce soit un appel à ToUpper ou n'importe quelle autre recette.

Adaptation aux types

Notre exemple s'adapte facilement à n'importe quel type de données.


// Méthode générique type - HOF qui fonctionne pour n'importe quel 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;
}

Usage :


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

5. Filtrage et agrégation via des fonctions d'ordre supérieur

La logique de filtrage et de recherche s'exprime naturellement via des HOF.


// Filtrage : fonction d'ordre supérieur
List<T> Filter<T>(List<T> items, Predicate<T> criteria)
{
    var result = new List<T>();
    foreach (var item in items)
    {
        if (criteria(item)) // On appelle la fonction-critère
        {
            result.Add(item);
        }
    }
    return result;
}

Utilisation :


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

6. Concept de composition de fonctions (function composition)

Les fonctions d'ordre supérieur permettent non seulement d'utiliser des fonctions individuelles, mais aussi de les enchaîner — les fameuses compositions. En C#, on peut implémenter ça avec une fonction qui prend deux fonctions et rend leur combinaison.


// Compositeur de fonctions : retourne une fonction qui applique d'abord g puis f
Func<T, TResult> Compose<T, TIntermediate, TResult>(
    Func<TIntermediate, TResult> f,
    Func<T, TIntermediate> g)
{
    return x => f(g(x));
}

// Exemple :
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. Petites astuces utiles

Contexte historique : pourquoi c'était plus dur avant ?

Avant les delegates et les lambdas, les devs écrivaient plein de boucles similaires, copiaient des bouts de code pour "filtrer", "transformer" ou "grouper" des données. Avec les fonctions d'ordre supérieur on peut extraire la partie variable du comportement en fonctions-paramètres — ce qui réduit drastiquement la duplication et rend le code plus expressif.

Un peu de sucre syntaxique : fonctions en expression

Les HOF sont souvent écrites avec des méthodes expression-bodied — des méthodes concises sur une ligne :


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

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

Comparaison avec les mécanismes LINQ

Voyons comment LINQ utilise des fonctions d'ordre supérieur :

Méthode LINQ Type de delegate accepté Rôle
Where
Func<T, bool>
Filtre les éléments
Select
Func<T, TResult>
Transforme les éléments
OrderBy
Func<T, TKey>
Trie par clé
Aggregate
Func<TAcc, T, TAcc>
Agrège (réduit) la collection
Any
Func<T, bool>
Vérifie la présence d'un élément selon une condition

Toutes ces méthodes reposent sur l'idée des fonctions d'ordre supérieur : tu écris les règles, la librairie standard fournit l'infrastructure.

8. Erreurs possibles et pièges

Confusion sur les types de delegates.
Au début il est facile de se perdre entre Action, Func et Predicate.
Astuce : si la fonction retourne bool — c'est souvent un Predicate. Si elle retourne une valeur — prends Func, si elle ne retourne rien — Action.

Captures de variables (closures).
Si la fonction retournée utilise des variables de l'environnement externe, veille à ce que les valeurs soient celles attendues au moment de l'appel. Les variables ne sont pas copiées, elles sont "capturées".

Déboguer des chaînes complexes.
Quand les fonctions sont composées en longs pipelines, il devient difficile de savoir quel maillon a transformé les données de façon incorrecte. Ajoute des affichages temporaires et des commentaires :


n => {
  Console.WriteLine("Avant opération : " + n);
  var res = n * 2;
  Console.WriteLine("Après : " + res);
  return res;
}
2
Mission
C# SELF, niveau 51, leçon 2
Bloqué
Filtrage avec des fonctions d'ordre supérieur
Filtrage avec des fonctions d'ordre supérieur
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION