¿cuándo y por qué utilizar delegados? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Soy relativamente nuevo en C#, y me pregunto cuándo usar Delegados apropiadamente. son ampliamente utilizados en la declaración de eventos , pero ¿cuándo debo usarlos en mi propio código y por qué son útiles? por qué no usar algo ¿else?

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

Gracias por la ayuda!

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

Author: iChaib, 2010-01-07

8 answers

Estoy de acuerdo con todo lo que ya se ha dicho, solo tratando de poner algunas otras palabras en él.

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

Al definir un delegado, le estás diciendo al usuario de tu clase, " Por favor, siéntete libre de asignar, cualquier método que coincida con esta firma, al delegado y será llamado cada vez que mi delegado sea llamado".

El uso típico es, por supuesto, eventos. Todos los OnEventX delegan a los métodos el usuario define.

Los delegados son útiles para ofrecer al usuario de sus objetos alguna habilidad para personalizar su comportamiento. La mayoría de las veces, puedes usar otras formas para lograr el mismo propósito y no creo que puedas ser forzado a crear delegados. Es simplemente la manera más fácil en algunas situaciones para conseguir la cosa hecha.

 232
Author: Benoit Vidis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-18 20:18:34

Un delegado es una referencia a un método. Mientras que los objetos se pueden enviar 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 ahí es cuando necesitará delegados.

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;
    }
  }
}
 256
Author: dhaval8087,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-04 05:46:38

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

Idealmente queremos alguna función que se parezca a:

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

Para que podamos pasar en cualquier Integrand, f , y obtener su integral definida sobre el intervalo cerrado.

¿Qué tipo debería ser Integrand?

Sin Delegados

Bien, sin delegados, 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 necesitaríamos 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

Entonces para usarlos en nuestro método Gauss3, necesitamos invocarlo de la siguiente manera:{[16]]}

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

Y Gauss3 necesita hacer el aspecto siguiente:

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

Así que necesitamos hacer todo eso solo para usar nuestras funciones arbitrarias en Guass3.

Con Delegados

public delegate double Integrand(double x);

Ahora podemos definir algunas funciones estáticas (o no) adheridas a ese prototipo:{[16]]}

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

No hay interfaces, no torpe .cosas de evaluación, sin instanciación de objetos, solo uso de puntero de función simple, para una tarea simple.

Por supuesto, los delegados son más que solo punteros de función bajo el capó, pero eso es un tema separado (encadenamiento de funciones y eventos).

 138
Author: Alex Budovski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-15 02:04:50

Los delegados son extremadamente útiles cuando se quiere declarar un bloque de código que se quiere pasar. Por ejemplo, cuando se utiliza un mecanismo de reintento genérico.

Pseudo:

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

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

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

 26
Author: Jan Jongboom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-07 09:56:42

Panorama general de los delegados

los Delegados tienen las siguientes propiedades:

  • Los delegados son similares a los punteros de función C++, pero son seguros de tipo.
  • Los delegados permiten que los métodos se pasen como parámetros.
  • Los delegados se pueden utilizar para definir métodos de devolución de llamada.
  • Los delegados se pueden encadenar juntos; por ejemplo, se pueden llamar 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, consulte Covarianza y contra varianza.
  • La versión 2.0 de C# introduce el concepto de Métodos Anónimos, que permiten pasar bloques de código como parámetros en lugar de un método definido por separado.
 21
Author: Lukas Šalkauskas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-13 00:06:15

Acabo de ir mi cabeza alrededor de estos, y por lo que voy a compartir un ejemplo, ya que ya tiene descripciones, pero por el momento una ventaja que veo es evitar las advertencias de estilo de Referencia Circular donde no puede tener 2 proyectos haciendo referencia entre sí.

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

Aquí tengo 2 proyectos que construyen mi solución: FTP y una base de datos SaveDatabase.

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

Ahora, nuestra aplicación necesita notificar al sitio FTP cuando un archivo se guarda en la base de datos cargando un archivo con Metadatos (ignore por qué, es una solicitud del propietario del sitio FTP). La cuestión es en qué momento 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 debe vivir en nuestro proyecto FTP. Pero, esto significaría nuestro NotifyFtpComplete tendrá que ser activado o, tendrá que esperar hasta que el guardado se complete, y luego consultar la base de datos para asegurarse de que está allí. Lo que necesitamos hacer es decirle a nuestro proyecto SaveDatabase que llame al método NotifyFtpComplete() directamente, pero no podemos; obtendríamos una referencia ciruclar y NotifyFtpComplete() es un método privado. Qué pena, esto habría funcionado. Bueno, puede.

Durante el código de nuestra aplicación, habríamos pasado parámetros entre métodos, pero lo que si uno de esos parámetros era el método NotifyFtpComplete. Sí, pasamos el método, con todo el código dentro. 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 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 se inicia 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();
    }
}

Así que, 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();
}

Le sugiero que revise el código y vea cuándo se llama _notice y cuándo se llama al método (delegate), ya que esto, espero, dejará las cosas muy 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 paramater 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);
}
 20
Author: Dave Rook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-22 08:23:19

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

 9
Author: Mark Seemann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-07 09:55:08

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

Si bien podría ser posible crear un conjunto extenso de código para realizar esta funcionalidad, no es necesario también. Puede utilizar un delegado.

Crear un delegado es fácil de hacer. Identificar la clase como una delega con la palabra clave "delegate". A continuación, especifique la firma del tipo.

 3
Author: Pankaj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-07 12:51:00