Patrón C # para evitar que un manejador de eventos se enganche dos veces

Duplicado de: ¿Cómo asegurar que un evento solo esté suscrito una vez y ya se haya agregado un controlador de eventos?

Tengo un singleton que proporciona algún servicio y mis clases se conectan a algunos eventos, a veces una clase se conecta dos veces al evento y luego recibe dos llamadas. Estoy buscando una forma clásica de evitar que esto suceda. de alguna manera tengo que verificar si ya me he conectado a este evento …

Implemente explícitamente el evento y verifique la lista de invocación. También necesitarás verificar null:

using System.Linq; // Required for the .Contains call below: ... private EventHandler foo; public event EventHandler Foo { add { if (foo == null || !foo.GetInvocationList().Contains(value)) { foo += value; } } remove { foo -= value; } } 

Usando el código anterior, si una persona que llama se suscribe al evento varias veces, simplemente se ignorará.

¿Qué tal si quitamos el evento primero con -= , si no se encuentra, no se lanza una excepción?

 /// -= Removes the event if it has been already added, this prevents multiple firing of the event ((System.Windows.Forms.WebBrowser)sender).Document.Click -= new System.Windows.Forms.HtmlElementEventHandler(testii); ((System.Windows.Forms.WebBrowser)sender).Document.Click += new System.Windows.Forms.HtmlElementEventHandler(testii); 

He probado cada solución y la mejor (considerando el rendimiento) es:

 private EventHandler _foo; public event EventHandler Foo { add { _foo -= value; _foo += value; } remove { _foo -= value; } } 

No se requiere el uso de Linq. No es necesario comprobar el nulo antes de cancelar una suscripción (ver MS EventHandler para más detalles). No hay necesidad de recordar hacer la cancelación en todas partes.

Realmente debería manejar esto en el nivel de receptor y no en el nivel de origen. Es decir, no prescriba la lógica del controlador de eventos en el origen del evento; déjelo en manos de los manejadores (los sumideros).

Como desarrollador de un servicio, ¿quién eres tú para decir que los sumideros solo pueden registrarse una vez? ¿Qué pasa si quieren registrarse dos veces por alguna razón? Y si está tratando de corregir errores en los sumideros modificando la fuente, de nuevo es una buena razón para corregir estos problemas en el nivel de sumidero.

Estoy seguro de que tienes tus razones; una fuente de eventos para los cuales los sumideros duplicados son ilegales no es insondable. Pero tal vez deberías considerar una architecture alternativa que deje intacta la semántica de un evento.

Debe implementar el complemento y eliminar los descriptores de acceso en el evento, y luego verificar la lista de destino del delegado, o almacenar los objectives en una lista.

En el método add, puede usar el método Delegate.GetInvocationList para obtener una lista de los objectives ya agregados al delegado.

Como los delegates están definidos para comparar iguales si están vinculados al mismo método en el mismo objeto de destino, probablemente pueda ejecutar esa lista y comparar, y si no encuentra ninguno que compare igual, agregue el nuevo.

Aquí está el código de muestra, comstackr como aplicación de consola:

 using System; using System.Linq; namespace DemoApp { public class TestClass { private EventHandler _Test; public event EventHandler Test { add { if (_Test == null || !_Test.GetInvocationList().Contains(value)) _Test += value; } remove { _Test -= value; } } public void OnTest() { if (_Test != null) _Test(this, EventArgs.Empty); } } class Program { static void Main() { TestClass tc = new TestClass(); tc.Test += tc_Test; tc.Test += tc_Test; tc.OnTest(); Console.In.ReadLine(); } static void tc_Test(object sender, EventArgs e) { Console.Out.WriteLine("tc_Test called"); } } } 

Salida:

 tc_Test called 

(es decir, solo una vez)

El marco de extensiones reactivas de Microsoft (Rx) también se puede usar para hacer “suscribirse solo una vez”.

Dado un evento de ratón foo.Click, aquí le mostramos cómo suscribirse y recibir solo una invocación:

 Observable.FromEvent(foo, "Clicked") .Take(1) .Subscribe(MyHandler); ... private void MyHandler(IEvent eventInfo) { // This will be called just once! var sender = eventInfo.Sender; var args = eventInfo.EventArgs; } 

Además de proporcionar la funcionalidad de “suscribirse una vez”, el enfoque RX ofrece la capacidad de componer eventos juntos o filtrar eventos. Es bastante ingenioso

Crea una acción en lugar de un evento. Tu clase puede verse así:

 public class MyClass { // sender arguments <----- Use this action instead of an event public Action OnSomeEventOccured; public void SomeMethod() { if(OnSomeEventOccured!=null) OnSomeEventOccured(this, null); } } 

haga que su objeto singleton verifique su lista de quién lo notifica y solo llame una vez si está duplicado. Alternativamente, si es posible, rechace la solicitud de archivo adjunto.

En Silverlight debe decir e.Handled = true; en el código del evento.

 void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { e.Handled = true; //this fixes the double event fire problem. string name = (e.OriginalSource as Image).Tag.ToString(); DoSomething(name); } 

Por favor, hágame un favor si esto ayuda.

Tal vez mi respuesta a una publicación similar ayude:

Diferenciar entre eventos generados por la interacción del usuario y mi propio código