2010-01-16 7 views
10

¿Es posible evitar que varios suscriptores se suscriban a un evento?¿Puede un detector de eventos limitarse a tener solo un suscriptor?

He creado un fragmento de ejemplo rápido para dar contexto a mi pregunta, pero desafortunadamente no puedo probarlo ahora porque no estoy en mi máquina VS.

El objetivo es:

  • devolver una lista vacía si no hay suscriptores.
  • Devuelve el valor de retorno de un único suscriptor.
  • Lanza una excepción si más de un suscriptor intenta suscribirse al evento (este es el quid de la cuestión).

¿Esto es posible?

public delegate List<IBaseWindow> GetWindowListDelegate(); 
public static event GetWindowListDelegate GetWindowListEvent; 

public List<IBaseWindow> GetWindowList() { 

    if (GetWindowListEvent == null) { 
     return new List<IBaseWindow>(); 
    } 

    return GetWindowListEvent(); 
} 

Nota: estoy usando .NET 3.5 SP1.

Respuesta

11

Parece que no necesita un evento: simplemente exponga al delegado y permita que las personas que llaman configuren la referencia del delegado por su cuenta.

+0

Lo siento pero no entiendo lo suficiente a los delegados como para saber a qué se refiere ... mi comprensión de los delegados se limita a su uso con los eventos. Tal vez eso ha limitado la forma en que me estoy acercando a este problema. ¿Podría dar un ejemplo mientras voy y leo a los delegados? – InvertedAcceleration

+2

No hay nada difícil de entender, solo elimine la palabra clave 'event', y" suscríbase "con = en vez de + = –

+8

AFAIK todos los delegados en .NET son por defecto delegados de transmisión múltiple, es decir, pueden tener múltiples" suscriptores ". – dtb

7

Puede usar event accessors para lograr esto. Algo como lo siguiente:

private EventHandler _h; 
    public event EventHandler H { 
     add { 
     if (...) { // Your conditions here. 
        // Warning (as per comments): clients may not 
        // expect problems to occur when adding listeners! 
      _h += value; 
     } 
     } 
     remove { 
     _h -= value; 
     } 
    } 

Como señala Andrew, realmente no necesita eventos para lograr esto. ¿Hay alguna razón particular por la que los necesitas?

+8

+1 como respuesta. Añadiría cuidado con esta técnica: cuando un desarrollador de .NET ve un tipo declarar un evento, la suposición * safe * (tan segura que ni siquiera hay una razón para comprobar lo contrario) es que se pueden agregar nuevos oyentes sin problemas. –

+0

+1 tanto para la respuesta como para el comentario por @ 280Z28. –

+0

Buena llamada. He agregado una nota. –

0

Sin duda es posible lograr lo que quiere hacer, pero no se ajusta a las convenciones. Le insto a que encuentre una solución diferente que no implique eventos.

As explained by Jon Skeet, los eventos públicos son envoltorios similares a propiedades alrededor de un delegado de multidifusión.

Puede pensar en la implementación estándar de un evento como una lista de funciones que se invocarán cuando algo suceda.

// From the above link: 

// the exposed event 
public event EventHandler MyEvent 

// multicast delegate field 
private EventHandler _myEvent; 

// property-like add & remove handlers 
public event EventHandler MyEvent 
{ 
    add 
    { 
     lock (this) 
     { 
      _myEvent += value; 
     } 
    } 
    remove 
    { 
     lock (this) 
     { 
      _myEvent -= value; 
     } 
    }   
} 

... cuando un desarrollador ve tal evento, que esperan ser capaces de suscribirse a él sin ningún problema como un evento básicamente dice "cualquier número de tipos puede registrarse para recibir esta notificación de eventos".

Parece que quiere permitir que alguien establezca la implementación que obtiene la lista de ventanas. Lo que sugiero hacer es permitir que las personas pasen manualmente un delegado, y luego mantener una única instancia delegado. Haga explícito que solo hay una forma de establecerlo; de ser posible, recomendaría usar la inyección de constructor, ya que elimina toda ambigüedad; solo puede establecer la instancia de delegado una vez en la construcción, y la API pública ya no puede modificarla (para que la clase B no pueda vencer al delegado que ya estaba configurado) por clase A).

E.g.

public class CallsDelegateToDoSomething 
{  
    private Func<List<IBaseWindow>> m_windowLister; 

    public CallsDelegateToDoSomething(Func<List<IBaseWindow>> windowFunc) 
    { 
     m_windowLister = windowFunc; 
    } 

    public List<IBaseWindow> GetWindowList() 
    {  
     if (windowLister == null) 
     { 
      return new List<IBaseWindow>(); 
     } 

     return m_windowLister(); 
    } 
} 

Si el diseño no permite esto, entonces basta con crear SetWindowLister(Func<List<IBaseWindow>> windowLister) y ClearWindowLister() métodos en su lugar.

+1

Incluso un "único delegado" puede dar como resultado la invocación de objetivos múltiples. – dtb

+0

Gracias por una respuesta tan completa y el último ejemplo. Echo un vistazo más a los delegados ahora para entender el comentario de dtb. – InvertedAcceleration

+0

dtb: a menos que se obtenga la lista de llamadas de invocación y se garantice que tiene un recuento de 1, ¿hay alguna forma elegante de garantizar que un delegado no sea un delegado de multiemisión? Usualmente trabajo usando interfaces para este tipo de cosas (por ejemplo, IWindowLister), por lo que resuelve ese problema en particular, pero significa que tienes que crear e implementar una interfaz, lo que no siempre es adecuado. –

5

sólo para completar la respuesta de Juan, aquí es una implementación funcional de un evento que sólo permite un manejador:

class Foo 
{ 
    private EventHandler _bar; 
    public event EventHandler Bar 
    { 
     add 
     { 
      if (_bar != null || value.GetInvocationList().Length > 1) 
      { 
       throw new InvalidOperationException("Only one handler allowed"); 
      } 
      _bar = (EventHandler)Delegate.Combine(_bar, value); 
     } 
     remove 
     { 
      _bar = (EventHandler)Delegate.Remove(_bar, value); 
     } 
    } 
} 

Tenga en cuenta que la exposición de un delegado en lugar de un evento no impide que varios controladores: dado que los delegados de .NET son multidifusión, un delegado puede representar una llamada a múltiples métodos. Sin embargo, podría exponer al delegado como una propiedad y realizar en el instalador el mismo control que en el código anterior.

De todos modos, como han señalado otros, probablemente no sea una buena idea evitar que haya múltiples manipuladores para un evento ... sería muy confuso para los desarrolladores que lo usan.

+0

Gracias por tomarse el tiempo extra para proporcionar más información. Definitivamente estoy decidido ahora que el diseño inicial fue incorrecto. – InvertedAcceleration

2

Con este código expone su delegado YourNameHere, pero se deshabilitará la funcionalidad + =, permitiendo SÓLO =.

private Action<byte[]> yourNameHere; 

public Action<byte[]> YourNameHere 
{ 
    set { yourNameHere= value; } 
} 

Espero que ayude.

Cuestiones relacionadas