Je suis relativement nouveau en C # et je me demande quand utiliser les délégués de manière appropriée . ils sont largement utilisés dans la déclaration d'événements, mais quand dois-je les utiliser dans mon propre code et pourquoi sont-ils utiles? ) pourquoi ne pas utiliser autre chose?
Je me demande aussi quand je dois utiliser des délégués et je n'ai pas d'autre alternative .
Merci pour l'aide!
EDIT: Je pense avoir trouvé un usage nécessaire des délégués ici
Je suis d'accord avec tout ce qui est déjà dit, j'essaie simplement de mettre d'autres mots dessus.
Un délégué peut être vu comme un espace réservé pour une/des méthodes.
En définissant un délégué, vous dites à l'utilisateur de votre classe: " S'il vous plaît, n'hésitez pas à affecter toute méthode correspondant à cette signature au délégué et ce dernier sera appelé à chaque fois que mon délégué s'appelle ".
L'utilisation typique est bien sûr les événements. Tous les délégués OnEventX à des méthodes définies par l'utilisateur .
Les délégués sont utiles pour offrir à l'utilisateur de vos objets la possibilité de personnaliser leur comportement. La plupart du temps, vous pouvez utiliser d'autres moyens pour atteindre le même objectif et je ne crois pas que vous puissiez être forcé de créer des délégués. Dans certains cas, il s’agit simplement de la manière la plus simple de faire avancer les choses.
Un délégué est une référence à une méthode. Alors que les objets peuvent facilement être envoyés en tant que paramètres dans des méthodes, constructeur ou autre, les méthodes sont un peu plus délicates. Mais de temps en temps, vous pouvez ressentir le besoin d’envoyer une méthode en tant que paramètre à une autre méthode et vous aurez alors besoin de délégués.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MyLibrary;
namespace DelegateApp {
/// <summary>
/// A class to define a person
/// </summary>
public class Person {
public string Name { get; set; }
public int Age { get; set; }
}
class Program {
//Our delegate
public delegate bool FilterDelegate(Person p);
static void Main(string[] args) {
//Create 4 Person objects
Person p1 = new Person() { Name = "John", Age = 41 };
Person p2 = new Person() { Name = "Jane", Age = 69 };
Person p3 = new Person() { Name = "Jake", Age = 12 };
Person p4 = new Person() { Name = "Jessie", Age = 25 };
//Create a list of Person objects and fill it
List<Person> people = new List<Person>() { p1, p2, p3, p4 };
//Invoke DisplayPeople using appropriate delegate
DisplayPeople("Children:", people, IsChild);
DisplayPeople("Adults:", people, IsAdult);
DisplayPeople("Seniors:", people, IsSenior);
Console.Read();
}
/// <summary>
/// A method to filter out the people you need
/// </summary>
/// <param name="people">A list of people</param>
/// <param name="filter">A filter</param>
/// <returns>A filtered list</returns>
static void DisplayPeople(string title, List<Person> people, FilterDelegate filter) {
Console.WriteLine(title);
foreach (Person p in people) {
if (filter(p)) {
Console.WriteLine("{0}, {1} years old", p.Name, p.Age);
}
}
Console.Write("\n\n");
}
//==========FILTERS===================
static bool IsChild(Person p) {
return p.Age < 18;
}
static bool IsAdult(Person p) {
return p.Age >= 18;
}
static bool IsSenior(Person p) {
return p.Age >= 65;
}
}
}
Supposons que vous souhaitiez écrire une procédure pour intégrer une fonction réelle f (x) sur un intervalle [a, b]. Supposons que nous voulions utiliser la méthode gaussienne à 3 points pour le faire (n'importe qui le fera, bien sûr).
Idéalement, nous voulons une fonction qui ressemble à:
// 'f' is the integrand we want to integrate over [a, b] with 'n' subintervals.
static double Gauss3(Integrand f, double a, double b, int n) {
double res = 0;
// compute result
// ...
return res;
}
Nous pouvons donc passer dans n'importe quelle Integrand
, f, et obtenir son intégrale définie sur l'intervalle fermé.
Quel type doit être Integrand
?
Eh bien, sans délégués, nous aurions besoin d’une sorte d’interface avec une seule méthode, disons eval
déclarée comme suit:
// Interface describing real-valued functions of one variable.
interface Integrand {
double eval(double x);
}
Ensuite, nous aurions besoin de créer tout un tas de classes implémentant cette interface, comme suit:
// Some function
class MyFunc1 : Integrand {
public double eval(double x) {
return /* some_result */ ;
}
}
// Some other function
class MyFunc2 : Integrand {
public double eval(double x) {
return /* some_result */ ;
}
}
// etc
Ensuite, pour les utiliser dans notre méthode Gauss3, nous devons l'invoquer comme suit:
double res1 = Gauss3(new MyFunc1(), -1, 1, 16);
double res2 = Gauss3(new MyFunc2(), 0, Math.PI, 16);
Et Gauss3 doit ressembler à ceci:
static double Gauss3(Integrand f, double a, double b, int n) {
// Use the integrand passed in:
f.eval(x);
}
Nous devons donc faire tout cela simplement pour utiliser nos fonctions arbitraires dans Guass3
.
public delegate double Integrand(double x);
Nous pouvons maintenant définir des fonctions statiques (ou non) adhérant à ce prototype:
class Program {
public delegate double Integrand(double x);
// Define implementations to above delegate
// with similar input and output types
static double MyFunc1(double x) { /* ... */ }
static double MyFunc2(double x) { /* ... */ }
// ... etc ...
public static double Gauss3(Integrand f, ...) {
// Now just call the function naturally, no f.eval() stuff.
double a = f(x);
// ...
}
// Let's use it
static void Main() {
// Just pass the function in naturally (well, its reference).
double res = Gauss3(MyFunc1, a, b, n);
double res = Gauss3(MyFunc2, a, b, n);
}
}
Pas d'interface, pas de trucs .eval encombrants, pas d'instanciation d'objet, juste un simple pointeur de fonction semblable à un usage, pour une tâche simple.
Bien entendu, les délégués ne sont pas que des indicateurs de fonction cachés, mais il s’agit d’une question distincte (enchaînement de fonctions et événements).
Les délégués sont extrêmement utiles lorsque vous souhaitez déclarer un bloc de code que vous souhaitez transmettre. Par exemple, lorsque vous utilisez un mécanisme de nouvelle tentative générique.
Pseudo:
function Retry(Delegate func, int numberOfTimes)
try
{
func.Invoke();
}
catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. }
Ou lorsque vous souhaitez effectuer une évaluation tardive des blocs de code, comme une fonction dans laquelle vous avez une action Transform
et souhaitez effectuer une action BeforeTransform
et AfterTransform
que vous pouvez évaluer dans votre environnement. Fonction de transformation, sans avoir à savoir si la BeginTransform
est remplie ou ce qu’elle doit transformer.
Et bien sûr, lors de la création de gestionnaires d'événements. Vous ne souhaitez pas évaluer le code maintenant, mais uniquement lorsque cela est nécessaire. Vous enregistrez donc un délégué pouvant être appelé lorsque l'événement se produit.
Je viens d’examiner ces questions et je vais donc donner un exemple car vous en avez déjà une description, mais pour l’instant, je vois un avantage à contourner les avertissements de style circulaire, dans lesquels vous ne pouvez pas avoir 2 projets référençant chacun. autre.
Supposons qu'une application télécharge un fichier XML, puis enregistre le fichier XML dans une base de données.
J'ai 2 projets ici qui construisent ma solution: FTP et SaveDatabase.
Ainsi, notre application commence par rechercher les téléchargements et télécharge le ou les fichiers, puis appelle le projet SaveDatabase.
Désormais, notre application doit informer le site FTP lorsqu'un fichier est enregistré dans la base de données en téléchargeant un fichier contenant des métadonnées (ignore pourquoi, il s'agit d'une demande du propriétaire du site FTP). La question est à quel point et comment? Nous avons besoin d'une nouvelle méthode appelée NotifyFtpComplete (), mais dans lequel de nos projets doit-elle également être enregistrée - FTP ou SaveDatabase? Logiquement, le code devrait vivre dans notre projet FTP. Mais cela signifierait que notre NotifyFtpComplete devra être déclenché ou attendre la fin de la sauvegarde, puis interroger la base de données pour s’assurer qu’elle se trouve bien dans celle-ci. Ce que nous devons faire, c'est dire à notre projet SaveDatabase d'appeler directement la méthode NotifyFtpComplete (), mais nous ne pouvons pas; nous obtiendrions une référence circulaire et la forme NotifyFtpComplete () est une méthode privée. Quel dommage, cela aurait fonctionné. Eh bien, ça peut.
Au cours du code de notre application, nous aurions passé des paramètres entre les méthodes, mais que se passerait-il si l'un de ces paramètres était la méthode NotifyFtpComplete. Oui, nous passons la méthode, avec tout le code à l'intérieur également. Cela signifierait que nous pourrions exécuter la méthode à tout moment, à partir de n'importe quel projet. Eh bien, voici ce que le délégué est. Cela signifie que nous pouvons transmettre la méthode NotifyFtpComplete () en tant que paramètre à notre classe SaveDatabase (). Au moment de la sauvegarde, il exécute simplement le délégué.
Voir si cet exemple brut aide (pseudo-code). Nous supposerons également que l'application commence par la méthode Begin () de la classe FTP.
class FTP
{
public void Begin()
{
string filePath = DownloadFileFromFtpAndReturnPathName();
SaveDatabase sd = new SaveDatabase();
sd.Begin(filePath, NotifyFtpComplete());
}
private void NotifyFtpComplete()
{
//Code to send file to FTP site
}
}
class SaveDatabase
{
private void Begin(string filePath, delegateType NotifyJobComplete())
{
SaveToTheDatabase(filePath);
/* InvokeTheDelegate -
* here we can execute the NotifyJobComplete
* method at our preferred moment in the application,
* despite the method being private and belonging
* to a different class.
*/
NotifyJobComplete.Invoke();
}
}
Donc, avec cela expliqué, nous pouvons le faire pour de vrai maintenant avec cette application console utilisant C #
using System;
namespace ConsoleApplication1
{
/* I've made this class private to demonstrate that
* the SaveToDatabase cannot have any knowledge of this Program class.
*/
class Program
{
static void Main(string[] args)
{
//Note, this NotifyDelegate type is defined in the SaveToDatabase project
NotifyDelegate nofityDelegate = new NotifyDelegate(NotifyIfComplete);
SaveToDatabase sd = new SaveToDatabase();
sd.Start(nofityDelegate);
Console.ReadKey();
}
/* this is the method which will be delegated -
* the only thing it has in common with the NofityDelegate
* is that it takes 0 parameters and that it returns void.
* However, it is these 2 which are essential.
* It is really important to notice that it writes
* a variable which, due to no constructor,
* has not yet been called (so _notice is not initialized yet).
*/
private static void NotifyIfComplete()
{
Console.WriteLine(_notice);
}
private static string _notice = "Notified";
}
public class SaveToDatabase
{
public void Start(NotifyDelegate nd)
{
/* I shouldn't write to the console from here,
* just for demonstration purposes
*/
Console.WriteLine("SaveToDatabase Complete");
Console.WriteLine(" ");
nd.Invoke();
}
}
public delegate void NotifyDelegate();
}
Je vous suggère de parcourir le code et de voir quand _notice est appelé et quand la méthode (delegate) est appelée car ceci, j'espère, rendra les choses très claires.
Cependant, enfin, nous pouvons le rendre plus utile en modifiant le type de délégué pour inclure un paramètre.
using System.Text;
namespace ConsoleApplication1
{
/* I've made this class private to demonstrate that the SaveToDatabase
* cannot have any knowledge of this Program class.
*/
class Program
{
static void Main(string[] args)
{
SaveToDatabase sd = new SaveToDatabase();
/* Please note, that although NotifyIfComplete()
* takes a string parameter, we do not declare it,
* all we want to do is tell C# where the method is
* so it can be referenced later,
* we will pass the parameter later.
*/
var notifyDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete);
sd.Start(notifyDelegateWithMessage );
Console.ReadKey();
}
private static void NotifyIfComplete(string message)
{
Console.WriteLine(message);
}
}
public class SaveToDatabase
{
public void Start(NotifyDelegateWithMessage nd)
{
/* To simulate a saving fail or success, I'm just going
* to check the current time (well, the seconds) and
* store the value as variable.
*/
string message = string.Empty;
if (DateTime.Now.Second > 30)
message = "Saved";
else
message = "Failed";
//It is at this point we pass the parameter to our method.
nd.Invoke(message);
}
}
public delegate void NotifyDelegateWithMessage(string message);
}
Vue d'ensemble des délégués
Les délégués ont les propriétés suivantes:
- Les délégués sont similaires aux pointeurs de fonction C++, mais sont sécurisés au type.
- Les délégués permettent aux méthodes d'être transmises en tant que paramètres.
- Les délégués peuvent être utilisés pour définir des méthodes de rappel.
- Les délégués peuvent être enchaînés; Par exemple, plusieurs méthodes peuvent être appelées sur un seul événement.
- Les méthodes n'ont pas besoin de correspondre exactement à la signature du délégué. Pour plus d'informations, voir Covariance et variance.
- C # version 2.0 introduit le concept de méthodes anonymes, qui permettent de transmettre des blocs de code en tant que paramètres à la place d'une méthode définie séparément.
Je considère les délégués comme étant interfaces anonymes . Dans de nombreux cas, vous pouvez les utiliser chaque fois que vous avez besoin d'une interface avec une seule méthode, mais vous ne voulez pas que ce soit la surcharge de la définition de cette interface.
Un délégué est une classe simple utilisée pour pointer vers des méthodes avec une signature spécifique, devenant ainsi essentiellement un pointeur de fonction de type. Le but d'un délégué est de faciliter un rappel vers une autre méthode (ou méthodes), une fois terminée, de manière structurée.
Bien qu’il soit possible de créer un ensemble complet de codes pour exécuter cette fonctionnalité, vous n’avez pas besoin de trop. Vous pouvez utiliser un délégué.
Créer un délégué est facile à faire. Identifiez la classe en tant que délégué avec le mot clé "delegate". Ensuite, spécifiez la signature du type.