2010-01-16 10 views
10

È possibile impedire a più abbonati di iscriversi a un evento?Un ascoltatore di eventi può avere un solo abbonato?

Ho creato un breve snippet di esempio per dare un po 'di contesto alla mia domanda ma sfortunatamente non riesco a testarlo in questo momento perché non sono nella mia macchina VS.

L'obiettivo è quello di:

  • Ritorna una lista vuota se non ci sono abbonati.
  • Restituisce il valore restituito di un singolo sottoscrittore.
  • Lanciare un'eccezione se più di un sottoscrittore tenta di iscriversi all'evento (questo è il nocciolo della questione).

È possibile?

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

public List<IBaseWindow> GetWindowList() { 

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

    return GetWindowListEvent(); 
} 

Nota: sto usando .NET 3.5 SP1.

risposta

11

Sembra che non sia necessario un evento: basta esporre il delegato stesso e consentire ai chiamanti di impostare il riferimento delegato autonomamente.

+0

Scusa ma non capisco abbastanza bene il delegato per sapere cosa intendi ... la mia comprensione dei delegati è limitata al loro uso con gli eventi. Forse questo ha limitato il modo in cui mi sto avvicinando a questo problema. Potresti fornire un esempio mentre vado a leggere i delegati? – InvertedAcceleration

+2

Non c'è niente di difficile da capire, basta rimuovere la parola chiave 'event' e" subscribe "con = invece di + = –

+8

AFAIK tutti i delegati in .NET sono di default delegati multi-cast, cioè possono avere più" sottoscrittori ". – dtb

7

Per eseguire questa operazione è possibile utilizzare event accessors. Qualcosa di simile a quanto segue:

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; 
     } 
    } 

Come Andrew fa notare, non si ha realmente bisogno di eventi per raggiungere questo obiettivo. C'è qualche ragione particolare per cui hai bisogno di loro?

+8

+1 come risposta. Aggiungerei a stare attenti con questa tecnica - quando uno sviluppatore .NET vede un tipo dichiarare un evento, l'assunzione * safe * (così sicura non c'è nemmeno una ragione per controllare altrimenti) è che i nuovi ascoltatori possono essere aggiunti senza problemi. –

+0

+1 sia per la risposta che per il commento di @ 280Z28. –

+0

Buona chiamata. Ho aggiunto una nota. –

0

È certamente possibile ottenere ciò che si vuole fare, ma non è conforme alle convenzioni - Vi esorto a proporre una soluzione diversa che non implichi eventi.

As explained by Jon Skeet, gli eventi pubblici sono involucri di proprietà attorno a un delegato multicast.

Si può pensare all'implementazione standard di un evento come una lista di funzioni da chiamare quando succede qualcosa.

// 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; 
     } 
    }   
} 

... quando uno sviluppatore vede un tale evento, si aspettano di essere in grado di iscriversi ad esso senza alcun problema come un evento in pratica dice "qualsiasi numero di tipi può registrarsi per ricevere la notifica evento".

Sembra che tu voglia consentire a qualcuno di impostare l'implementazione che ottiene l'elenco delle finestre. Quello che suggerirei di fare è consentire alle persone di passare manualmente in un delegato, quindi di tenere un'istanza delegata singola. Rendi esplicito che esiste un solo modo per impostarlo; se possibile, consiglierei di usare il constructor injection in quanto rimuove ogni ambiguità - puoi solo impostare l'istanza delegata una volta in costruzione, quindi non è più modificabile dall'API pubblica (quindi la classe B non può clobare il delegato che era già impostato per classe 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(); 
    } 
} 

Se il progetto non consente questo, allora basta creare invece SetWindowLister(Func<List<IBaseWindow>> windowLister) e ClearWindowLister() metodi.

+1

Anche un "singolo delegato" può comportare l'invocazione di più destinazioni. – dtb

+0

Grazie per la risposta completa e l'esempio finale. Sto ora dando un'occhiata ai delegati per capire il commento di dtb. – InvertedAcceleration

+0

dtb: in mancanza di ottenere l'elenco di chiamate e assicurandosi che abbia un conteggio pari a 1, esiste un modo elegante per garantire che un delegato non sia un delegato multi-cast? Di solito lavoro usando le interfacce per questo genere di cose (ad es. IWindowLister), in modo tale da risolvere quel particolare problema ma significa che devi invece creare e implementare un'interfaccia, che non è sempre adatta. –

5

solo per completare la risposta di Giovanni, ecco un'implementazione di lavoro di un evento che consente a un solo gestore:

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); 
     } 
    } 
} 

Nota che esporre un delegato piuttosto che un evento non impedisce più gestori: dal delegati .NET sono multicast, un delegato può rappresentare una chiamata a più metodi. È tuttavia possibile esporre il delegato come una proprietà ed eseguire nel setter lo stesso assegno del codice sopra riportato.

In ogni caso, come altri hanno sottolineato, probabilmente non è una buona idea prevenire più gestori per un evento ... sarebbe molto confuso per gli sviluppatori che lo utilizzano.

+0

Grazie per aver dedicato del tempo extra per fornire ulteriori informazioni. Sono assolutamente d'accordo ora che il progetto iniziale era sbagliato. – InvertedAcceleration

2

Con questo codice esponi il tuo delegato YourNameHere, ma disabiliterà la funzionalità + =, consentendo SOLO =.

private Action<byte[]> yourNameHere; 

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

Spero che sia d'aiuto.

Problemi correlati