2009-06-01 7 views
73

Duplicado de: How to ensure an event is only subscribed to once y Has an event handler already been added?C# patrón para evitar un controlador de eventos enganchó dos veces

tengo un producto único que proporciona algún servicio y mis clases enganchan en algunos eventos en él, a veces una clase es conectar dos veces para el evento y luego se llama dos veces. 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 ...

Respuesta

120

Implemente explícitamente el evento y compruebe la lista de invocación. También tendrá que comprobar para nulo:

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 ignora.

+13

Necesita usar System.Linq usando. –

+0

Solo para aclarar el comentario de Hermann; debe incluir el espacio de nombres 'System.Linq' agregando 'using System.Linq' a su clase o espacio de nombres actual. –

+0

Fascinante. LINQ sigue siendo lo suficientemente nuevo para mí que tengo que buscarlo y recordarle que significa Language Integrated Query ... y luego me pregunto qué tiene que ver eso con EventHandlers y su InvocationList. – fortboise

12

Debe implementar agregar y quitar accesos en el evento, y luego verificar la lista de objetivos del delegado, o almacenar los objetivos en un lista.

En el método add, puede usar el método Delegate.GetInvocationList para obtener una lista de los objetivos que ya se han agregado al delegado.

Dado que los delegados se definen para comparar iguales si están vinculados al mismo método en el mismo objeto de destino, probablemente podría ejecutar esa lista y comparar, y si no encuentra ninguno que compare igual, agregue el nuevo .

Aquí es código de ejemplo, los datos 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, sólo una vez).

+0

Por favor, ignore mi comentario, olvidé el uso de Linq. –

+0

Solución más limpia (aunque no la más corta). – Shimmy

0

tienen comprobar su objeto único es la lista de los que se notifica y solo llama una vez si está duplicado. Alternativamente, si es posible, rechace la solicitud de archivo adjunto.

17

Usted realmente debe manejar esto a nivel fregadero y no al nivel de la fuente. 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 dice 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 tiene sus razones; una fuente de eventos para los cuales los sumideros duplicados son ilegales no es insondable. Pero tal vez deberías considerar una arquitectura alternativa que deje intacta la semántica de un evento.

+0

Esta es una excelente justificación de ingeniería para [esta solución] (http://stackoverflow.com/a/1104269/3367144), que resuelve el problema en el lado del receptor de eventos (suscriptor/consumidor/observador/manejador) en lugar de la fuente lado. – kdbanman

140

¿Qué tal sólo la eliminación de caso primero con -=, si no se encuentra una excepción no se lanza

/// -= 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); 
+0

Gracias. Esto fue muy útil en el mismo escenario (WebBrowser + HtmlElementEventHandler). Gracias por señalar esto – Odys

+0

Esta debería ser la respuesta aceptada, ya que es simple y no requiere una implementación personalizada. @LoxLox muestra el mismo patrón que una implementación también. No hice la prueba, así que estoy tomando los comentarios en su palabra. Muy agradable. – Rafe

+3

+1 Supongo que depende del programador, pero diría que es el mejor (no el más "adecuado" para requerir que el desarrollador final lo haga, pero no es culpa del generador de eventos que el suscriptor no pueda evitarlo múltiples suscripciones, entonces, haga que descubran la eliminación, etc ... además, ¿por qué impedir que alguien suscriba el mismo controlador más de una vez si lo desea?) –

6

de Reactive Extensions (Rx) framework Microsoft también se puede utilizar para hacer "subscribe una sola vez".

Dado un evento de ratón foo.Clicked, aquí es cómo suscribirse y recibir una única invocación:

Observable.FromEvent<MouseEventArgs>(foo, "Clicked") 
    .Take(1) 
    .Subscribe(MyHandler); 

... 

private void MyHandler(IEvent<MouseEventArgs> eventInfo) 
{ 
    // This will be called just once! 
    var sender = eventInfo.Sender; 
    var args = eventInfo.EventArgs; 
} 

Además de proporcionar "subscribe vez" funcionalidad, el enfoque RX ofrece la capacidad de componer juntos eventos o filtrar eventos. Es bastante ingenioso

+0

Aunque esto es técnicamente correcto, responde la pregunta incorrecta. – AlexFoxGill

0

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, activa conmigo si esto ayuda.

1

Crea una acción en lugar de un evento. Su clase puede verse como:

public class MyClass 
{ 
       // sender arguments  <-----  Use this action instead of an event 
    public Action<object, EventArgs> OnSomeEventOccured; 

    public void SomeMethod() 
    { 
      if(OnSomeEventOccured!=null) 
       OnSomeEventOccured(this, null); 
    } 

} 
20

He probado cada solución y la mejor (teniendo en cuenta el rendimiento) es:

private EventHandler _foo; 
public event EventHandler Foo { 

    add { 
     _foo -= value; 
     _foo += value; 
    } 
    remove { 
     _foo -= value; 
    } 
} 

Sin LINQ utilizando necesaria. 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.

Cuestiones relacionadas