Petit complément concernant les "fonctions d'ordre supérieur".
Dans les langages dits "fonctionnels" (LISP, F#, OCaml, Haskell, etc.), les fonctions sont des objets de "première classe", c'est à dire que les fonctions sont traitées comme n'importe quel autre objet. Il est donc naturel qu'une fonction puisse prendre comme argument une autre fonction ou retourner une fonction.
C# (comme VB) n'est pas un langage fonctionnel et, pour pouvoir passer une méthode comme argument d'une autre méthode, recours à l'utilisa tion de
délégués.
Un délégué (
delegate en anglais) est un objet de la classe
Delegate (ou d'une classe qui en hérite) qui permet d'appeler une méthode qui n'est pas forcément toujours la même mais qui doit respecter la signature du délégué, c'est à dire que la méthode doit avoir le même nombre d'arguments du même type et le même type de valeur de retour.
Les gestionnaires d'évènements, par exemple, sont des délégués de type
EventHandler (ou d'une classe qui en dérive) qui doivent être des méthodes (procédures dirait on en VB) qui ne retournent rien (
void) et qui acceptent deux arguments, le premier de type Object, le second de type EventArgs (ou d'une classe qui en hérite).
La signature de la fonction SelectMany utilisée dans l'exemple est :
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector)
Le type de retour,
IEnumerable<TResult>, est une collection d'objets de type
TResult (Line dans l'exemple ci-dessus) et les arguments sont :
-
IEnumerable<TSource> : la collection d'objets de type TSource (ObjectId dans l'exemple) à laquelle s'applique la fonction ;
-
Func<TSource, IEnumerable<TResult>> : la fonction passée en argument.
Func (comme
Action) est un délégué générique, sa signature indique que la fonction doit prendre un argument de type
TSource et retourner une collection d'objet de type
IEnumerable<TResult>.
C'est
SelectMany qui concatène les collections de
TResult retournées par
Func pour chaque
TSource contenu dans la collection à laquelle s'applique
SelectMany.
Pour passer les fonctions en argument dans les exemples ci-dessus, on a utilisé des
expressions lambda. Si ceci peut être familier pour les LISPeurs (avec la fonction lambda, justement) ou pour les utilisateurs d'autres langages fonctionnels, ça peut être un peu perturbant pour d'autres.
L'utilisation d'expressions lambda (ou de fonctions anonymes comme nous le verrons) n'est pas une obligation, mais présente l'intérêt, outre la concision du code, de définir la fonction à l'endroit où elle est exécutée, ce qui peut rendre le code plus lisible (surtout quand les fonctions sont très courtes).
On peut très bien passer en argument une méthode définie ailleurs dans le code pour autant que cette méthode respecte la signature (c'est ce que l'on fait habituellement avec les gestionnaires d'évènement).
Par exemple, le traitement des lignes, argument de la méthode Iterate() dans le dernier exemple aurait pu être défini dans une méthode séparée qui aurait été appelée directement dans Iterate().
public void CmdExt()
{
...
.Iterate(ToLayer0);
...
}
private void ToLayer0(Line line)
{
line.UpgradeOpen();
line.Layer = "0";
}
On aurait aussi pu, utiliser une méthode anonyme, mais, à mon avis, ça ne présent que peu d'intérêt par rapport à une expression lambda :
.Iterate(delegate(Line line) { line.UpgradeOpen(); line.Layer = "0"; });