Délégués et expressions lambda
Un délégué définit un type qui représente des références aux méthodes qui ont une liste spécifique de paramètres et un type de retour. Méthode (statique ou instance) dont la liste de paramètres et la correspondance de type de retour peuvent être attribuées à une variable de ce type, puis appelées directement (avec les arguments appropriés) ou passées comme argument à une autre méthode, puis appelées. L’exemple suivant montre l’utilisation des délégués.
using System;
using System.Linq;
public class Program
{
public delegate string Reverse(string s);
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Reverse rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
- La ligne
public delegate string Reverse(string s);
crée un type délégué d’une méthode qui prend un paramètre de type chaîne, puis retourne un paramètre de type chaîne. - La méthode
static string ReverseString(string s)
, qui a exactement la même liste de paramètres et le même type de retour que le type délégué défini, implémente le délégué. - La ligne
Reverse rev = ReverseString;
montre que vous pouvez affecter une méthode à une variable du type délégué correspondant. - La ligne
Console.WriteLine(rev("a string"));
montre comment utiliser une variable d’un type délégué pour appeler le délégué.
Pour simplifier le processus de développement, .NET inclut un ensemble de types délégués que les programmeurs peuvent réutiliser sans avoir à en créer. Ces types sont Func<>
, Action<>
et Predicate<>
, et ils peuvent être utilisés sans avoir à définir de nouveaux types délégués. Il existe quelques différences entre les trois types, qui ont à voir avec la façon dont ils sont destinés à être utilisés :
Action<>
est utilisé quand une action doit être effectuée à l’aide des arguments du délégué. La méthode qu’il encapsule ne retourne pas de valeur.Func<>
est généralement utilisé dans le cas d’une transformation, autrement dit, quand vous devez transformer les arguments du délégué en un résultat différent. Les projections en sont un bon exemple. La méthode qu’il encapsule retourne une valeur spécifiée.Predicate<>
est utilisé quand vous devez déterminer si l’argument répond à la condition du délégué. Elle peut également être écrite en tant queFunc<T, bool>
, ce qui signifie que la méthode retourne une valeur booléenne.
Nous pouvons maintenant reprendre notre exemple ci-dessus et le réécrire à l’aide du délégué Func<>
au lieu d’un type personnalisé. Le programme continue à s’exécuter exactement de la même façon.
using System;
using System.Linq;
public class Program
{
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}
static void Main(string[] args)
{
Func<string, string> rev = ReverseString;
Console.WriteLine(rev("a string"));
}
}
Dans cet exemple simple, il peut sembler un peu superflu de définir une méthode en dehors de la méthode Main
. .NET Framework 2.0 a introduit le concept de délégués anonymes, qui vous permet de créer des délégués « inline » sans avoir à spécifier de type ou de méthode supplémentaire.
Dans l’exemple suivant, un délégué anonyme filtre une liste uniquement sur les nombres pairs, puis les imprime dans la console.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(
delegate (int no)
{
return (no % 2 == 0);
}
);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
Comme vous pouvez le voir, le corps du délégué n’est qu’un ensemble d’expressions, comme tout autre délégué. Mais au lieu d’en faire une définition distincte, nous l’avons introduit ad hoc dans notre appel à la méthode List<T>.FindAll.
Toutefois, même avec cette approche, nous avons encore beaucoup de code inutile. C’est là que les expressions lambda interviennent. Les expressions lambda ont été introduites dans C# 3.0 comme l’un des principaux blocs de construction de la requête LINQ (Language-Integrated Query). Il s’agit simplement d’une syntaxe plus pratique pour l’utilisation des délégués. Elles déclarent une liste de paramètres et un corps de méthode, mais n’ont pas d’identité formelle propre, sauf si elles sont attribuées à un délégué. Contrairement aux délégués, elles peuvent être directement affectées comme côté droit de l’inscription d’un événement, ou dans plusieurs clauses et méthodes LINQ.
Dans la mesure où une expression lambda est simplement une autre façon de spécifier un délégué, nous pouvons réécrire l’exemple ci-dessus pour utiliser une expression lambda au lieu d’un délégué anonyme.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
List<int> list = new List<int>();
for (int i = 1; i <= 100; i++)
{
list.Add(i);
}
List<int> result = list.FindAll(i => i % 2 == 0);
foreach (var item in result)
{
Console.WriteLine(item);
}
}
}
Dans l’exemple précédent, l’expression lambda utilisée est i => i % 2 == 0
. Là encore, il s’agit simplement d’une syntaxe pratique pour l’utilisation de délégués. Par conséquent, ce qui se passe en coulisses est très proche de ce qui se passe avec le délégué anonyme.
Comme les expressions lambda sont juste des délégués, elles peuvent servir de gestionnaire d’événements sans aucun problème, comme l’illustre l’extrait de code suivant.
public MainWindow()
{
InitializeComponent();
Loaded += (o, e) =>
{
this.Title = "Loaded";
};
}
Dans ce contexte, l’opérateur +=
est utilisé pour s’abonner à un événement. Pour plus d’informations, consultez Guide pratique pour s’abonner et annuler l’abonnement à des événements.