¿Cuándo y por qué usar delegates?

Soy relativamente nuevo en C #, y me pregunto cuándo usar Delegados de manera adecuada . son ampliamente utilizados en la statement de eventos, pero ¿cuándo debería usarlos en mi propio código y por qué son útiles? ¿por qué no usar algo más?

También me pregunto cuándo tengo que usar delegates y no tengo otra alternativa .

¡Gracias por la ayuda!

EDITAR: Creo que he encontrado un uso necesario de Delegados aquí

Estoy de acuerdo con todo lo que ya se ha dicho, solo bash ponerle otras palabras.

Un delegado puede verse como un marcador de posición para / algunos método (s).

Al definir un delegado, le dice al usuario de su clase: ” Por favor, siéntase libre de asignar cualquier método que coincida con esta firma al delegado, y se lo llamará cada vez que se llame a mi delegado “.

El uso típico es, por supuesto, de eventos. Todo el delegado de OnEventX a los métodos que define el usuario.

Los delegates son útiles para ofrecer al usuario de sus objetos la posibilidad de personalizar su comportamiento. La mayoría de las veces, puede usar otras formas para lograr el mismo propósito y no creo que pueda verse obligado a crear delegates. Es la forma más fácil en algunas situaciones para hacer el trabajo.

Un delegado es una referencia a un método. Mientras que los objetos pueden enviarse fácilmente como parámetros a métodos, constructores o lo que sea, los métodos son un poco más complicados. Pero de vez en cuando puede sentir la necesidad de enviar un método como parámetro a otro método, y es entonces cuando necesitará delegates.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using MyLibrary; namespace DelegateApp { ///  /// A class to define a person ///  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 people = new List() { p1, p2, p3, p4 }; //Invoke DisplayPeople using appropriate delegate DisplayPeople("Children:", people, IsChild); DisplayPeople("Adults:", people, IsAdult); DisplayPeople("Seniors:", people, IsSenior); Console.Read(); } ///  /// A method to filter out the people you need ///  /// A list of people /// A filter /// A filtered list static void DisplayPeople(string title, List 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; } } } 

Supongamos que desea escribir un procedimiento para integrar alguna función de valor real f ( x ) en algún intervalo [a, b]. Digamos que queremos usar el método gaussiano de 3 puntos para hacer esto (cualquiera lo hará, por supuesto).

Lo ideal es que queramos alguna función que se vea así:

 // '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; } 

De modo que podemos pasar cualquier Integrand , f , y obtener su integral definida durante el intervalo cerrado.

¿Qué tipo debería ser Integrand ?

Sin Delegados

Bueno, sin delegates, necesitaríamos algún tipo de interfaz con un solo método, digamos eval declarado de la siguiente manera:

 // Interface describing real-valued functions of one variable. interface Integrand { double eval(double x); } 

Entonces tendríamos que crear un montón de clases implementando esta interfaz, de la siguiente manera:

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

Luego, para usarlos en nuestro método Gauss3, debemos invocarlo de la siguiente manera:

 double res1 = Gauss3(new MyFunc1(), -1, 1, 16); double res2 = Gauss3(new MyFunc2(), 0, Math.PI, 16); 

Y Gauss3 necesita parecerse a lo siguiente:

 static double Gauss3(Integrand f, double a, double b, int n) { // Use the integrand passed in: f.eval(x); } 

Entonces tenemos que hacer todo eso solo para usar nuestras funciones arbitrarias en Guass3 .

Con los delegates

 public delegate double Integrand(double x); 

Ahora podemos definir algunas funciones estáticas (o no) que se adhieren a ese prototipo:

 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); } } 

Sin interfaces, sin elementos emocionales complicados, sin creación de instancias de objetos, solo con funciones simples como el uso, para una tarea simple.

Por supuesto, los delegates son más que simples indicadores de función bajo el capó, pero eso es un problema aparte (función de encadenamiento y eventos).

Los delegates son extremadamente útiles cuando desean declarar un bloque de código que desea pasar. Por ejemplo, cuando se utiliza un mecanismo de rebash genérico.

Seudo:

 function Retry(Delegate func, int numberOfTimes) try { func.Invoke(); } catch { if(numberOfTimes blabla) func.Invoke(); etc. etc. } 

O cuando desee realizar una evaluación tardía de bloques de código, como una función donde tenga alguna acción Transform , y desee tener una BeforeTransform y una acción AfterTransform que pueda evaluar dentro de su función Transform, sin tener que saber si BeginTransform está lleno , o lo que tiene que transformar

Y, por supuesto, al crear controladores de eventos. No desea evaluar el código ahora, pero solo cuando sea necesario, por lo que registra un delegado que se puede invocar cuando se produce el evento.

Visión general de los delegates

Los delegates tienen las siguientes propiedades:

  • Los delegates son similares a los punteros de función de C ++, pero son de tipo seguro.
  • Los delegates permiten que los métodos pasen como parámetros.
  • Los delegates se pueden usar para definir métodos de callback.
  • Los delegates se pueden encadenar juntos; por ejemplo, se pueden invocar múltiples métodos en un solo evento.
  • Los métodos no necesitan coincidir exactamente con la firma del delegado. Para obtener más información, vea Covarianza y varianza de Contra.
  • La versión 2.0 de C # introduce el concepto de métodos anónimos, que permite pasar bloques de código como parámetros en lugar de un método definido por separado.

Los he analizado, así que compartiré un ejemplo, ya que tienes descripciones, pero por el momento, una de las ventajas que veo es evitar las advertencias de estilo de referencia circular, donde no puedes tener 2 proyectos haciendo referencia a cada uno. otro.

Supongamos que una aplicación descarga un XML y luego guarda el XML en una base de datos.

Tengo 2 proyectos aquí que construyen mi solución: FTP y SaveDatabase.

Entonces, nuestra aplicación comienza buscando cualquier descarga y descarga de archivo (s) luego llama al proyecto SaveDatabase.

Ahora, nuestra aplicación necesita notificar al sitio FTP cuando se guarda un archivo en la base de datos cargando un archivo con metadatos (ignore por qué, es una solicitud del propietario del sitio FTP). El problema es en qué punto y cómo? Necesitamos un nuevo método llamado NotifyFtpComplete (), pero ¿en cuál de nuestros proyectos debería guardarse también: FTP o SaveDatabase? Lógicamente, el código debería vivir en nuestro proyecto de FTP. Pero esto significaría que nuestro NotifyFtpComplete tendrá que activarse o, tendrá que esperar hasta que se complete el guardado, y luego consultar la base de datos para asegurarse de que esté ahí. Lo que tenemos que hacer es decirle a nuestro proyecto SaveDatabase que llame directamente al método NotifyFtpComplete (), pero no podemos; obtendríamos una referencia ciruclar y NotifyFtpComplete () es un método privado. Qué lástima, esto hubiera funcionado. Bueno, puede.

Durante el código de nuestra aplicación, hubiéramos pasado parámetros entre métodos, pero ¿qué ocurre si uno de esos parámetros es el método NotifyFtpComplete? Sí, pasamos el método, con todo el código dentro también. Esto significaría que podríamos ejecutar el método en cualquier punto, desde cualquier proyecto. Bueno, esto es lo que es el delegado. Esto significa que podemos pasar el método NotifyFtpComplete () como un parámetro a nuestra clase SaveDatabase (). En el punto que guarda, simplemente ejecuta el delegado.

Vea si este ejemplo crudo ayuda (pseudo código). También asumiremos que la aplicación comienza con el método Begin () de la clase 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(); } } 

Entonces, con eso explicado, podemos hacerlo de verdad ahora con esta aplicación de consola usando 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) { Console.WriteLine("Yes, I shouldn't write to the console from here, it's just to demonstrate the code executed."); Console.WriteLine("SaveToDatabase Complete"); Console.WriteLine(" "); nd.Invoke(); } } public delegate void NotifyDelegate(); } 

Sugiero que revisen el código y vean cuándo se llama _notice, y cuando el método (delegado) se llame así, espero, dejarán las cosas claras.

Sin embargo, por último, podemos hacerlo más útil cambiando el tipo de delegado para incluir un parámetro.

 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 patwigter later. NotifyDelegateWithMessage nofityDelegateWithMessage = new NotifyDelegateWithMessage(NotifyIfComplete); sd.Start(nofityDelegateWithMessage); 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); } 

Considero que los delegates son interfaces anónimas . En muchos casos, puede usarlos siempre que necesite una interfaz con un único método, pero no desea la sobrecarga de definir esa interfaz.

Un delegado es una clase simple que se utiliza para apuntar a métodos con una firma específica, convirtiéndose esencialmente en un puntero de función de seguridad de tipo. El propósito de un delegado es facilitar una callback a otro método (o métodos), una vez que se haya completado, de una manera estructurada.

Si bien es posible crear un amplio conjunto de códigos para realizar esta funcionalidad, no es necesario. Puedes usar un delegado.

Crear un delegado es fácil de hacer. Identifique la clase como un delegado con la palabra clave “delegar”. Luego, especifique la firma del tipo.