Comprender eventos y controladores de eventos en C #

Entiendo el propósito de los eventos, especialmente en el contexto de la creación de interfaces de usuario. Creo que este es el prototipo para crear un evento:

public void EventName(object sender, EventArgs e); 

¿Qué hacen los manejadores de eventos, por qué son necesarios y cómo puedo crear uno?

Para entender a los manejadores de eventos, debes entender a los delegates . En C # , puede pensar en un delegado como un puntero (o una referencia) para un método. Esto es útil porque el puntero se puede pasar como un valor.

El concepto central de un delegado es su firma, o forma. Eso es (1) el tipo de devolución y (2) los argumentos de entrada. Por ejemplo, si creamos un delegado void MyDelegate(object sender, EventArgs e) , solo puede apuntar a los métodos que devuelven el void , y tomar un object y EventArgs . Algo así como un agujero cuadrado y una clavija cuadrada. Entonces decimos que estos métodos tienen la misma firma, o forma, que el delegado.

Entonces, al saber cómo crear una referencia a un método, pensemos en el propósito de los eventos: queremos hacer que se ejecute algún código cuando sucede algo en otro lugar del sistema, o “manejar el evento”. Para hacer esto, creamos métodos específicos para el código que queremos que se ejecute. El pegamento entre el evento y los métodos que se ejecutarán son los delegates. El evento debe almacenar internamente una “lista” de punteros a los métodos para llamar cuando se produce el evento. * Por supuesto, para poder llamar a un método, necesitamos saber qué argumentos se le pasan. Usamos al delegado como el “contrato” entre el evento y todos los métodos específicos que serán llamados.

Por lo tanto, el EventHandler predeterminado (y muchos similares) representa una forma específica de método (de nuevo, void / object-EventArgs). Cuando declaras un evento, estás diciendo qué forma de método (EventHandler) invocará ese evento, al especificar un delegado:

 //This delegate can be used to point to methods //which return void and take a string. public delegate void MyEventHandler(string foo); //This event can cause any method which conforms //to MyEventHandler to be called. public event MyEventHandler SomethingHappened; //Here is some code I want to be executed //when SomethingHappened fires. void HandleSomethingHappened(string foo) { //Do some stuff } //I am creating a delegate (pointer) to HandleSomethingHappened //and adding it to SomethingHappened's list of "Event Handlers". myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened); //To raise the event within a method. SomethingHappened("bar"); 

(* Esta es la clave de los eventos en .NET y quita la “magia”: un evento es realmente, bajo las sábanas, solo una lista de métodos de la misma “forma”. La lista se almacena donde vive el evento. el evento está “planteado”, simplemente es “ir a través de esta lista de métodos y llamar a cada uno, usando estos valores como parámetros”. Asignar un controlador de eventos es una forma más bonita y sencilla de agregar su método a esta lista de métodos ser llamado).

C # conoce dos términos, delegate y event . Comencemos con el primero.

Delegar

Un delegate es una referencia a un método. Al igual que puede crear una referencia a una instancia:

 MyClass instance = myFactory.GetInstance(); 

Puede usar un delegado para crear una referencia a un método:

 Action myMethod = myFactory.GetInstance; 

Ahora que tiene esta referencia a un método, puede llamar al método a través de la referencia:

 MyClass instance = myMethod(); 

¿Pero por qué lo harías? También puede simplemente llamar a myFactory.GetInstance() directamente. En este caso puedes. Sin embargo, hay muchos casos en los que no desea que el rest de la aplicación tenga conocimiento de myFactory o llame a myFactory.GetInstance() directamente.

Uno obvio es si desea poder reemplazar myFactory.GetInstance() en myOfflineFakeFactory.GetInstance() desde un lugar central (también conocido como patrón de método de fábrica ).

Patrón de método de fábrica

Entonces, si tiene una clase TheOtherClass y necesita utilizar myFactory.GetInstance() , así es como se verá el código sin delegates (deberá informar a TheOtherClass sobre el tipo de su myFactory ):

 TheOtherClass toc; //... toc.SetFactory(myFactory); class TheOtherClass { public void SetFactory(MyFactory factory) { // set here } } 

Si usa delegates, no tiene que exponer el tipo de mi fábrica:

 TheOtherClass toc; //... Action factoryMethod = myFactory.GetInstance; toc.SetFactoryMethod(factoryMethod); class TheOtherClass { public void SetFactoryMethod(Action factoryMethod) { // set here } } 

Por lo tanto, puede asignarle un delegado a otra clase, sin exponer su tipo a ellos. Lo único que expones es la firma de tu método (cuántos parámetros tienes y tal).

“Firma de mi método”, ¿dónde escuché eso antes? O sí, interfaces !!! las interfaces describen la firma de una clase completa. ¡Piense en los delegates que describen la firma de un solo método!

Otra gran diferencia entre una interfaz y un delegado es que cuando escribe su clase, no tiene que decirle a C # “este método implementa ese tipo de delegado”. Con las interfaces, debe decir “esta clase implementa ese tipo de interfaz”.

Además, una referencia de delegado puede (con algunas restricciones, ver a continuación) hacer referencia a múltiples métodos (llamado MulticastDelegate ). Esto significa que cuando llame al delegado, se ejecutarán varios métodos explícitamente adjuntos. Una referencia de objeto siempre puede hacer referencia solo a un objeto.

Las restricciones para un MulticastDelegate son que la firma (método / delegado) no debe tener ningún valor de retorno ( void ) y las palabras clave out y ref no se usan en la firma. Obviamente, no puede llamar a dos métodos que devuelven un número y esperan que devuelvan el mismo número. Una vez que la firma cumpla, el delegado se MulticastDelegate automáticamente en MulticastDelegate .

Evento

Los eventos son solo propiedades (como el get; set; propiedades de los campos de instancia) que exponen la suscripción al delegado de otros objetos. Estas propiedades, sin embargo, no admiten get; set ;. En su lugar, admiten agregar; eliminar;

Entonces puedes tener:

  Action myField; public event Action MyProperty { add { myField += value; } remove { myField -= value; } } 

Uso en UI (WinForms)

Entonces, ahora sabemos que un delegado es una referencia a un método y que podemos tener un evento para hacerle saber al mundo que pueden darnos sus métodos para que nuestro delegado se haga referencia a ellos, y somos un botón de IU, entonces: Puede preguntar a cualquiera que esté interesado en si me hicieron clic, para registrar su método con nosotros (a través del evento que expusimos). Podemos usar todos los métodos que se nos dieron, y hacer referencia a ellos por nuestro delegado. Y luego, esperaremos y esperaremos … hasta que un usuario acceda y haga clic en ese botón, entonces tendremos motivos suficientes para invocar al delegado. Y debido a que el delegado hace referencia a todos los métodos que se nos han dado, se invocarán todos esos métodos. No sabemos qué hacen esos métodos, ni sabemos qué clase implementa esos métodos. Lo único que nos importa es que alguien estaba interesado en que nos hicieran clic y nos dio una referencia a un método que cumplía con nuestra firma deseada.

Java

Los idiomas como Java no tienen delegates. Usan interfaces en su lugar. La forma en que lo hacen es preguntar a cualquier persona que esté interesada en “hacer clic en nosotros”, implementar una determinada interfaz (con un cierto método que podemos llamar) y luego darnos la instancia completa que implementa la interfaz. Mantenemos una lista de todos los objetos que implementan esta interfaz, y podemos llamar su ‘cierto método al que podemos llamar’ cada vez que recibimos un clic.

Aquí hay un ejemplo de código que puede ayudar:

 using System; using System.Collections.Generic; using System.Text; namespace Event_Example { // First we have to define a delegate that acts as a signature for the // function that is ultimately called when the event is triggered. // You will notice that the second parameter is of MyEventArgs type. // This object will contain information about the triggered event. public delegate void MyEventHandler(object source, MyEventArgs e); // This is a class which describes the event to the class that receives it. // An EventArgs class must always derive from System.EventArgs. public class MyEventArgs : EventArgs { private string EventInfo; public MyEventArgs(string Text) { EventInfo = Text; } public string GetInfo() { return EventInfo; } } // This next class is the one which contains an event and triggers it // once an action is performed. For example, lets trigger this event // once a variable is incremented over a particular value. Notice the // event uses the MyEventHandler delegate to create a signature // for the called function. public class MyClass { public event MyEventHandler OnMaximum; private int i; private int Maximum = 10; public int MyValue { get { return i; } set { if(value <= Maximum) { i = value; } else { // To make sure we only trigger the event if a handler is present // we check the event to make sure it's not null. if(OnMaximum != null) { OnMaximum(this, new MyEventArgs("You've entered " + value.ToString() + ", but the maximum is " + Maximum.ToString())); } } } } } class Program { // This is the actual method that will be assigned to the event handler // within the above class. This is where we perform an action once the // event has been triggered. static void MaximumReached(object source, MyEventArgs e) { Console.WriteLine(e.GetInfo()); } static void Main(string[] args) { // Now lets test the event contained in the above class. MyClass MyObject = new MyClass(); MyObject.OnMaximum += new MyEventHandler(MaximumReached); for(int x = 0; x <= 15; x++) { MyObject.MyValue = x; } Console.ReadLine(); } } } 

Esa es realmente la statement para un manejador de eventos, un método que se llamará cuando se active un evento. Para crear un evento, escribiría algo como esto:

 public class Foo { public event EventHandler MyEvent; } 

Y luego puedes suscribirte al evento de esta manera:

 Foo foo = new Foo(); foo.MyEvent += new EventHandler(this.OnMyEvent); 

Con OnMyEvent () definido así:

 private void OnMyEvent(object sender, EventArgs e) { MessageBox.Show("MyEvent fired!"); } 

Cada vez que Foo dispara MyEvent , se OnMyEvent su controlador OnMyEvent .

No siempre tiene que usar una instancia de EventArgs como segundo parámetro. Si desea incluir información adicional, puede usar una clase derivada de EventArgs ( EventArgs es la base por convención). Por ejemplo, si observa algunos de los eventos definidos en Control en WinForms o FrameworkElement en WPF, puede ver ejemplos de eventos que pasan información adicional a los controladores de eventos.

Solo para agregar a las grandes respuestas existentes aquí – basándose en el código en el aceptado, que usa un delegate void MyEventHandler(string foo)

Como el comstackdor conoce el tipo de delegado del evento SomethingHappened , este:

 myObj.SomethingHappened += HandleSomethingHappened; 

Es totalmente equivalente a:

 myObj.SomethingHappened += new MyEventHandler(HandleSomethingHappened); 

Y los manejadores también pueden ser no registrados con -= como esto:

 // -= removes the handler from the event's list of "listeners": myObj.SomethingHappened -= HandleSomethingHappened; 

Para completar, el evento se puede hacer de esta manera, solo en la clase propietaria del evento:

 //Firing the event is done by simply providing the arguments to the event: var handler = SomethingHappened; // thread-local copy of the event if (handler != null) // the event is null if there are no listeners! { handler("Hi there!"); } 

La copia local del manejador es necesaria para garantizar que la invocación es segura para los subprocesos; de lo contrario, un subproceso podría ir y anular el registro del último controlador para el evento inmediatamente después de comprobar si era null , y nos divertiríamos. NullReferenceException allí.


C # 6 introdujo una buena mano corta para este patrón. Utiliza el operador de propagación nulo.

 SomethingHappened?.Invoke("Hi there!"); 

Mi comprensión de los eventos es;

Delegar:

Una variable para mantener la referencia al método / métodos que se ejecutarán. Esto hace posible pasar métodos como una variable.

Pasos para crear y llamar al evento:

  1. El evento es una instancia de un delegado

  2. Como un evento es una instancia de un delegado, primero tenemos que definir el delegado.

  3. Asignar el método / métodos que se ejecutarán cuando se active el evento ( Llamando al delegado )

  4. Dispara el evento ( llama al delegado )

Ejemplo:

 using System; namespace test{ class MyTestApp{ //The Event Handler declaration public delegate void EventHandler(); //The Event declaration public event EventHandler MyHandler; //The method to call public void Hello(){ Console.WriteLine("Hello World of events!"); } public static void Main(){ MyTestApp TestApp = new MyTestApp(); //Assign the method to be called when the event is fired TestApp.MyHandler = new EventHandler(TestApp.Hello); //Firing the event if (TestApp.MyHandler != null){ TestApp.MyHandler(); } } } } 

editor: donde suceden los eventos. El editor debe especificar qué delegado utiliza la clase y generar los argumentos necesarios, pasar esos argumentos y a sí mismo al delegado.

suscriptor: donde ocurre la respuesta. El suscriptor debe especificar métodos para responder a los eventos. Estos métodos deben tomar el mismo tipo de argumentos que el delegado. El suscriptor luego agrega este método al delegado del editor.

Por lo tanto, cuando el evento ocurra en editorial, el delegado recibirá algunos argumentos de eventos (datos, etc.), pero el editor no tiene idea de qué sucederá con todos estos datos. Los suscriptores pueden crear métodos en su propia clase para responder a los eventos en la clase del editor, de modo que los suscriptores puedan responder a los eventos del editor.

 //This delegate can be used to point to methods //which return void and take a string. public delegate void MyDelegate(string foo); //This event can cause any method which conforms //to MyEventHandler to be called. public event MyDelegate MyEvent; //Here is some code I want to be executed //when SomethingHappened fires. void MyEventHandler(string foo) { //Do some stuff } //I am creating a delegate (pointer) to HandleSomethingHappened //and adding it to SomethingHappened's list of "Event Handlers". myObj.MyEvent += new MyDelegate (MyEventHandler); 

Estoy de acuerdo con KE50, excepto que veo la palabra clave ‘evento’ como un alias para ‘ActionCollection’ ya que el evento contiene una colección de acciones que se realizarán (es decir, el delegado).

 using System; namespace test{ class MyTestApp{ //The Event Handler declaration public delegate void EventAction(); //The Event Action Collection //Equivalent to // public List EventActions=new List(); // public event EventAction EventActions; //An Action public void Hello(){ Console.WriteLine("Hello World of events!"); } //Another Action public void Goodbye(){ Console.WriteLine("Goodbye Cruel World of events!"); } public static void Main(){ MyTestApp TestApp = new MyTestApp(); //Add actions to the collection TestApp.EventActions += TestApp.Hello; TestApp.EventActions += TestApp.Goodbye; //Invoke all event actions if (TestApp.EventActions!= null){ //this peculiar syntax hides the invoke TestApp.EventActions(); //using the 'ActionCollection' idea: // foreach(EventAction action in TestApp.EventActions) // action.Invoke(); } } } }