Introduction aux Délégués
Un délégué est un type de référence qui encapsule une méthode avec une signature spécifique. Il permet de traiter les méthodes comme des objets, de les stocker dans des variables et de les passer en paramètres. Les événements, quant à eux, sont une implémentation spécifique et sécurisée basée sur les délégués multicast.
- Déclaration des Délégués
1.1 Le mot-clé delegate
La déclaration classique permet de définir une signature personnalisée. Elle accepte jusqu'à 32 paramètres et peut retourner n'importe quel type, y compris void.
// Signature personnalisée pour le calcul de surface
public delegate double SurfaceCalculator(double width, double height);
1.2 Le délégué Action
Action est un délégué générique intégré qui ne retourne aucune valeur (void). Il peut accepter de 0 à 16 paramètres d'entrée.
// Exemple d'utilisation avec un paramètre générique
public void ExecuteTask<T>(Action<T> task, T payload)
{
task(payload);
}
1.3 Le délégué Func
Func est conçu pour les opérations qui renvoient un résultat. Le dernier argument générique représente toujours le type de retour. Il supporte jusqu'à 16 paramètres d'entrée.
// Transformation de données avec retour
public TResult ApplyTransformation<TInput, TResult>(Func<TInput, TResult> transformer, TInput data)
{
return transformer(data);
}
1.4 Le délégué Predicate
Spécialisé dans l'évaluation de conditions, Predicate<T> prend exactement un paramètre d'entrée et retourne toujours un booléen.
// Signature équivalente
public delegate bool ConditionEvaluator<T>(T item);
- Mise en Pratique
2.1 Utilisation de delegate
public delegate double MathOperation(double a, double b);
class Program
{
static void Main()
{
MathOperation op = new MathOperation(Multiply);
Console.WriteLine($"Résultat : {op(5.5, 2.0)}");
}
static double Multiply(double x, double y) => x * y;
}
2.2 Utilisation de Action
Idéal pour les opérations de type "fire-and-forget", le traitement de données ou les callbacks sans retour.
static void Main()
{
ProcessUser("Alice", user => Console.WriteLine($"Utilisateur connecté : {user}"));
ProcessUser(42, id => Console.WriteLine($"ID traité : {id}"));
}
public static void ProcessUser<T>(Action<T> handler, T data)
{
handler(data);
}
2.3 Utilisation de Func
static void Main()
{
double finalPrice = CalculateDiscount(150.0, price => price * 0.85);
Console.WriteLine($"Prix final : {finalPrice}");
}
public static double CalculateDiscount(Func<double, double> discountLogic, double initialPrice)
{
return discountLogic(initialPrice);
}
2.4 Utilisation de Predicate
Très utile pour le filtrage de collections, notamment avec les méthodes de recherche intégrées de la classe Array ou List<T>.
struct Transaction
{
public string Id;
public double Amount;
}
static void Main()
{
Transaction[] ledger = {
new Transaction { Id = "T1", Amount = 120.50 },
new Transaction { Id = "T2", Amount = 850.00 },
new Transaction { Id = "T3", Amount = 45.00 }
};
// Recherche de la première transaction supérieure à 500
Transaction highValue = Array.Find(ledger, IsHighValue);
Console.WriteLine($"Trouvé : {highValue.Id} pour {highValue.Amount}");
}
static bool IsHighValue(Transaction t) => t.Amount > 500.0;
- Nettoyage et Gestion de la Mémoire
Les délégués multicast peuvent conserver des références fortes à des objets, empêchant le garbage collector de les récupérer. Il est crucial de savoir les nettoyer pour éviter les fuites de mémoire.
3.1 Réinitialisation directe
La méthode la plus simple et la plus radicale pour vider complètement une chaîne de délégués est de la réassigner à null.
public MathOperation OnOperation;
public void ClearAllOperations()
{
OnOperation = null;
}
3.2 Suppression ciblée via GetInvocationList
Si vous devez retirer des abonnés spécifiques d'un événement ou d'un délégué multicast sans perdre les autres, parcourez la liste d'invocation.
public MathOperation OnOperation;
public void UnsubscribeAll()
{
if (OnOperation != null)
{
Delegate[] subscribers = OnOperation.GetInvocationList();
foreach (MathOperation subscriber in subscribers)
{
OnOperation -= subscriber;
}
}
}
- Caractéristiques Fondamentales
- Sécurité de type : Contrairement aux pointeurs de fonction en C++, les délégués C# sont strictement vérifiés par le compilateur au moment de la compilation.
- Injection de comportement : Ils permettent de découpler la logique métier en passant des algorithmes dynamiques en paramètres.
- Multicast : Un seul délégué peut invoquer une chaîne de plusieurs méthodes séquentielelment via les opérateurs
+et-. - Covariance et Contravariance : La signature de la méthode cible n'a pas besoin de correspondre exactement à celle du délégué, offrant une grande flexibilité grâce aux conversions implicites de types de référence.