Delegados, ¿Por qué?

Posibles duplicados:
¿Cuándo usarías delegates en C #?
El propósito de los delegates

He visto muchas preguntas sobre el uso de delegates. Todavía no tengo claro dónde y POR QUÉ usaría delegates en lugar de llamar al método directamente.

He escuchado esta frase muchas veces: “El objeto delegado se puede pasar al código que puede llamar al método al que se hace referencia, sin tener que saber en tiempo de comstackción qué método se invocará”.

No entiendo cómo esa statement es correcta.

He escrito los siguientes ejemplos. Digamos que tienes 3 métodos con los mismos parámetros:

public int add(int x, int y) { int total; return total = x + y; } public int multiply(int x, int y) { int total; return total = x * y; } public int subtract(int x, int y) { int total; return total = x - y; } 

Ahora declaro un delegado:

 public delegate int Operations(int x, int y); 

Ahora puedo dar un paso más y declarar a un controlador para usar este delegado (o su delegado directamente)

Llamar delegado:

 MyClass f = new MyClass(); Operations p = new Operations(f.multiply); p.Invoke(5, 5); 

o llame con el controlador

 f.OperationsHandler = f.multiply; //just displaying result to text as an example textBoxDelegate.Text = f.OperationsHandler.Invoke(5, 5).ToString(); 

En estos dos casos, veo que se especifica mi método de “multiplicación”. ¿Por qué las personas usan la frase “cambiar funcionalidad en tiempo de ejecución” o la anterior?

¿Por qué se utilizan los delegates si cada vez que declaro un delegado, necesita un método para señalar? y si necesita un método para señalar, ¿por qué no simplemente llamar a ese método directamente? Me parece que tengo que escribir más código para usar delegates que solo usar las funciones directamente.

¿Puede alguien por favor darme una situación del mundo real? Estoy totalmente confundido.

Cambiar la funcionalidad en tiempo de ejecución no es lo que los delegates logran.

Básicamente, los delegates le ahorran un montón de tipeo.

Por ejemplo:

 class Person { public string Name { get; } public int Age { get; } public double Height { get; } public double Weight { get; } } IEnumerable people = GetPeople(); var orderedByName = people.OrderBy(p => p.Name); var orderedByAge = people.OrderBy(p => p.Age); var orderedByHeight = people.OrderBy(p => p.Height); var orderedByWeight = people.OrderBy(p => p.Weight); 

En el código anterior, p => p.Name , p => p.Age , etc. son todas expresiones lambda que evalúan a Func delegates (donde T es string , int , double y double , respectivamente )

Ahora consideremos cómo podríamos haber logrado lo anterior sin delegates. En lugar de hacer que el método OrderBy tome un parámetro delegado, tendríamos que abandonar la genericidad y definir estos métodos:

 public static IEnumerable OrderByName(this IEnumerable people); public static IEnumerable OrderByAge(this IEnumerable people); public static IEnumerable OrderByHeight(this IEnumerable people); public static IEnumerable OrderByWeight(this IEnumerable people); 

Esto sería totalmente una mierda . Quiero decir, en primer lugar, el código se ha vuelto infinitamente menos reutilizable, ya que solo se aplica a colecciones del tipo Person . Además, tenemos que copiar y pegar el mismo código cuatro veces, cambiando solo 1 o 2 líneas en cada copia (donde se hace referencia a la propiedad relevante de la Person ; de lo contrario, todo se vería igual). Esto se convertiría rápidamente en un desastre inmanejable.

Por lo tanto, los delegates le permiten hacer que su código sea más reutilizable y más fácil de mantener abstrayendo ciertos comportamientos dentro del código que se puede activar y desactivar.

Delegados de .NET: AC # Bedtime Story

Los delegates son extremadamente útiles, especialmente después de la introducción de linq y cierres.

Un buen ejemplo es la función ‘Dónde’, uno de los métodos de linq estándar. ‘Dónde’ toma una lista y un filtro, y devuelve una lista de los elementos que coinciden con el filtro. (El argumento de filtro es un delegado que toma una T y devuelve un valor booleano).

Debido a que usa un delegado para especificar el filtro, la función Dónde es extremadamente flexible. No necesita diferentes funciones Where para filtrar números impares y números primos, por ejemplo. La syntax de llamada también es muy concisa, lo que no sería el caso si utilizara una interfaz o una clase abstracta.

Más concretamente, cuando tomar un delegado significa que puede escribir esto:

 var result = list.Where(x => x != null); ... 

en lugar de esto:

 var result = new List(); foreach (var e in list) if (e != null) result.add(e) ... 

¿Por qué se utilizan delegates si cada vez que declaro un delegado, necesita un método para señalar? y si necesita un método para señalar, ¿por qué no simplemente llamar a ese método directamente?

Al igual que las interfaces, los delegates le permiten desacoplar y generalizar su código. Por lo general, utiliza delegates cuando no sabe de antemano qué métodos desea ejecutar, cuando solo sabe que querrá ejecutar algo que coincida con una determinada firma.

Por ejemplo, considere una clase de temporizador que ejecutará algún método a intervalos regulares:

 public delegate void SimpleAction(); public class Timer { public Timer(int secondsBetweenActions, SimpleAction simpleAction) {} } 

Puede enchufar cualquier cosa en ese temporizador, para que pueda usarlo en cualquier otro proyecto o aplicación sin intentar predecir cómo lo usará y sin limitar su uso a un pequeño puñado de escenarios en los que esté pensando en este momento .

Déjame ofrecerte un ejemplo. Si su clase expone un event , se le puede asignar cierto número de delegates en el tiempo de ejecución, que se llamará para indicar que sucedió algo. Cuando escribiste la clase, no tenías idea de qué delegates terminaría funcionando. En cambio, esto es determinado por quien usa tu clase.

Para dar un ejemplo concreto, un uso particularmente reciente de un delegado para mí fue SendAsync() en System.Net.Mail.SmtpClient . Tengo una aplicación que envía toneladas y toneladas de correo electrónico y hubo un golpe de rendimiento notable a la espera de que el servidor de Exchange acepte el mensaje. Sin embargo, fue necesario registrar el resultado de la interacción con ese servidor.

Así que escribí un método de delegado para manejar ese registro y lo pasé a SendAsync() (anteriormente estábamos usando Send() ) al enviar cada correo electrónico. De esta forma, puede volver a llamar al delegado para registrar el resultado y los subprocesos de la aplicación no esperan que finalice la interacción antes de continuar.

Lo mismo puede suceder con cualquier IO externo en el que desee que la aplicación continúe sin esperar a que se complete la interacción. Las clases proxy para servicios web, etc. aprovechan esto.

Un ejemplo en el que se necesita un delegate es cuando tiene que modificar un control en el hilo de la interfaz de usuario y está operando en un hilo diferente. Por ejemplo,

 public delegate void UpdateTextBox(string data); private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { ... Invoke(new UpdateTextBox(textBoxData), data); ... } private void textBoxData(string data) { textBox1.Text += data; } 

En su ejemplo, una vez que ha asignado un delegado a una variable, puede pasarlo como cualquier otra variable. Puede crear un método que acepte un delegado como parámetro y puede invocar al delegado sin necesidad de saber dónde se declara realmente el método.

 private int DoSomeOperation( Operations operation ) { return operation.Invoke(5,5); } ... MyClass f = new MyClass(); Operations p = new Operations(f.multiply); int result = DoSomeOperation( p ); 

Los delegates convierten los métodos en cosas que puedes transmitir de la misma manera que un int. Podría decir que las variables no le dan nada adicional porque en

 int i = 5; Console.Write( i + 10 ); 

ve que se especifica el valor 5, por lo que también Console.Write( 5 + 10 ) decir Console.Write( 5 + 10 ) . Es cierto en ese caso, pero pierde los beneficios de poder decir

 DateTime nextWeek = DateTime.Now.AddDays(7); 

en lugar de tener que definir un método específico DateTime.AddSevenDays() y un método AddSixDays , y así sucesivamente.

Puede usar delegates para implementar suscripciones y eventHandlers. También puedes (de una manera terrible) usarlos para evitar las dependencias circulares.

O si tiene un motor de cálculo y hay muchos cálculos posibles, puede usar un delegado de parámetros en lugar de muchas llamadas a funciones diferentes para su motor.

Usando su ejemplo de Operations , imagine una calculadora que tenga varios botones. Podrías crear una clase para tu botón como esta

 class CalcButton extends Button { Operations myOp; public CalcButton(Operations op) { this.myOp=op; } public void OnClick(Event e) { setA( this.myOp(getA(), getB()) ); // perform the operation } } 

y luego cuando creas botones, puedes crear cada uno con una operación diferente

 CalcButton addButton = new CalcButton(new Operations(f.multiply)); 

Esto es mejor por varias razones. No duplicas el código en los botones, son generics. Puede tener varios botones que tengan la misma operación, por ejemplo, en diferentes paneles o menús. Puede cambiar la operación asociada con un botón sobre la marcha.

Los delegates se utilizan para resolver un problema de acceso. Siempre que desee tener un objeto foo que necesite llamar al método frob de la barra de objetos pero no acceda al método frob.

Object goo tiene acceso tanto a foo como a la barra para que pueda unirlos mediante delegates. Por lo general, bar y goo suelen ser el mismo objeto.

Por ejemplo, una clase Button normalmente no tiene acceso a la clase y define un método Button_click.

Entonces, ahora que tenemos eso, podemos usarlo para muchas cosas además de solo eventos. Los patrones Asynch y Linq son dos ejemplos.

Parece que muchas de las respuestas tienen que ver con los delegates en línea, que en mi opinión son más fáciles de entender de lo que llamaré “delegates clásicos”.

A continuación se muestra mi ejemplo de cómo los delegates permiten que una clase consumidora modifique o aumente el comportamiento (al agregar efectivamente “ganchos” para que el consumidor pueda hacer cosas antes o después de una acción crítica y / o evitar ese comportamiento por completo). Observe que toda la lógica de toma de decisiones se proporciona desde fuera de la clase StringSaver . Ahora considere que puede haber 4 consumidores diferentes de esta clase, cada uno de ellos puede implementar su propia lógica de Verification y Notification , o ninguno, según corresponda.

 internal class StringSaver { public void Save() { if(BeforeSave != null) { var shouldProceed = BeforeSave(thingsToSave); if(!shouldProceed) return; } BeforeSave(thingsToSave); // do the save if (AfterSave != null) AfterSave(); } IList thingsToSave; public void Add(string thing) { thingsToSave.Add(thing); } public Verification BeforeSave; public Notification AfterSave; } public delegate bool Verification(IEnumerable thingsBeingSaved); public delegate void Notification(); public class SomeUtility { public void SaveSomeStrings(params string[] strings) { var saver = new StringSaver { BeforeSave = ValidateStrings, AfterSave = ReportSuccess }; foreach (var s in strings) saver.Add(s); saver.Save(); } bool ValidateStrings(IEnumerable strings) { return !strings.Any(s => s.Contains("RESTRICTED")); } void ReportSuccess() { Console.WriteLine("Saved successfully"); } } 

Supongo que el punto es que el método al que apunta el delegado no está necesariamente en la clase que expone al miembro delegado.