2009-04-07 12 views
52

Sto imparando su Eventi/Delegati in C#. Posso chiedere la tua opinione sullo stile di denominazione/codifica che ho scelto (tratto dal libro Head First C#)?Eventi - convenzione di denominazione e stile

Sto insegnando a un amico su questo domani, e sto cercando di trovare il modo più elegante di spiegare i concetti. (Pensato che il modo migliore per capire un soggetto è cercare di insegnare!)

class Program 
    { 
     static void Main() 
     { 
      // setup the metronome and make sure the EventHandler delegate is ready 
      Metronome metronome = new Metronome(); 

      // wires up the metronome_Tick method to the EventHandler delegate 
      Listener listener = new Listener(metronome); 
      metronome.OnTick(); 
     } 
    } 

public class Metronome 
    { 
     // a delegate 
     // so every time Tick is called, the runtime calls another method 
     // in this case Listener.metronome_Tick 
     public event EventHandler Tick; 

     public void OnTick() 
     { 
      while (true) 
      { 
       Thread.Sleep(2000); 
       // because using EventHandler delegate, need to include the sending object and eventargs 
       // although we are not using them 
       Tick(this, EventArgs.Empty); 
      } 
     } 
    } 

public class Listener 
    { 
     public Listener(Metronome metronome) 
     { 
      metronome.Tick += new EventHandler(metronome_Tick); 
     } 

     private void metronome_Tick(object sender, EventArgs e) 
     { 
      Console.WriteLine("Heard it"); 
     } 
    } 

N.B. Il codice è refactored da http://www.codeproject.com/KB/cs/simplesteventexample.aspx

risposta

46

ci sono alcuni punti che vorrei citare:

Metronome.OnTick non sembra essere nominato in modo corretto. Semanticamente, "OnTick" mi dice che verrà chiamato quando "Tick", ma non è proprio quello che sta succedendo. Lo chiamerei invece "Vai".

Il modello generalmente accettato, tuttavia, sarebbe quello di fare quanto segue. OnTick è un metodo virtuale che solleva l'evento. In questo modo, puoi facilmente ignorare il comportamento predefinito nelle classi ereditate e chiamare la base per aumentare l'evento.

class Metronome 
{ 
    public event EventHandler Tick; 

    protected virtual void OnTick(EventArgs e) 
    { 
     //Raise the Tick event (see below for an explanation of this) 
     var tickEvent = Tick; 
     if(tickEvent != null) 
      tickEvent(this, e); 
    } 

    public void Go() 
    { 
     while(true) 
     { 
      Thread.Sleep(2000); 
      OnTick(EventArgs.Empty); //Raises the Tick event 
     } 
    } 
} 

Inoltre, so che questo è un esempio semplice, ma se non ci sono ascoltatori collegati, il codice getterà su Tick(this, EventArgs.Empty). Si dovrebbe includere almeno una guardia nulla per controllare per gli ascoltatori:

if(Tick != null) 
    Tick(this, EventArgs.Empty); 

Tuttavia, questo è ancora vulnerabile in un ambiente multithreading se l'ascoltatore è registrato tra la guardia e l'invocazione. La cosa migliore sarebbe quella di catturare gli ascoltatori attuali prima e li chiamano:

var tickEvent = Tick; 
if(tickEvent != null) 
    tickEvent(this, EventArgs.Empty); 

So che questa è una risposta vecchia, ma dal momento che è ancora la raccolta upvotes, ecco il modo in cui C# 6 di fare le cose. Il concetto intero "guardia" può essere sostituito con una chiamata di metodo condizionale e il compilatore effettivamente fare la cosa giusta (TM) per quanto riguarda la cattura gli ascoltatori:

Tick?.Invoke(this, EventArgs.Empty); 
+12

Alternativa alla guardia è aggiungere "= delegate {};" to Tick declaration (si veda http://stackoverflow.com/questions/231525/raising-c-events-with-an-extension-method-is-it-bad/231536#231536) – Benjol

+0

Si noti che la vulnerabilità non è _limitata_ ambienti multithread. È possibile (se sociopatico) per un gestore di eventi rimuovere tutti i gestori da un evento, causando un arresto anomalo quando il gestore viene completato e l'invocazione di un evento tenta di eseguire l'evento successivo (ora non esistente). –

+0

@GregD: C'è un modo per renderlo idiotproof tale che il codice client non possa farlo? –

2

Sembra buono, a parte il fatto che OnTick non segue il tipico modello di chiamata di evento. In genere, On[EventName] genera l'evento una sola volta, come

protected virtual void OnTick(EventArgs e) 
{ 
    if(Tick != null) Tick(this, e); 
} 

Considerare la creazione di questo metodo, e rinominare il metodo esistente "OnTick" a "StartTick", e invece di invocare Tick direttamente da StartTick, chiamare OnTick(EventArgs.Empty) dal StartTick metodo.

4

Un punto che ho trovato dopo l'utilizzo di eventi. Net per molti anni è la necessità ripetitiva di controllare l'evento per un gestore null su ogni invocazione. Devo ancora vedere un pezzo di codice live che fa tutto tranne che chiamare l'evento se è nullo.

Quello che ho iniziato a fare è mettere un gestore fittizio su ogni evento che creo per salvare la necessità di fare il controllo nullo.

public class Metronome 
{ 
    public event EventHandler Tick =+ (s,e) => {}; 

    protected virtual void OnTick(EventArgs e) 
    { 
     Tick(this, e); // now it's safe to call without the null check. 
    } 
} 
+1

Una cosa da sottolineare è che questo non funziona con la serializzazione. L'idea del delegato vuota è discussa a fondo su http://stackoverflow.com/questions/9033/hidden-features-of-c/9282#9282 –

+1

Può essere ancora più semplice: 'evento pubblico EventHandler Tick = delegate {};' – Mikhail

56

Microsoft ha in realtà scritto una vasta serie di linee guida per la denominazione e la inserisce nella libreria MSDN. Potete trovare gli articoli qui: Guidelines for Names

A parte le linee guida generali di capitalizzazione, ecco quello che ha per 'Eventi' nella pagina Names of Type Members:

fare Eventi nome con un verbo o una frase verbo .

Fornire nomi evento al concetto di prima e dopo, utilizzando il presente e il tempo passato. Ad esempio, un evento di chiusura che viene generato prima della chiusura di una finestra viene chiamato Closing e uno che viene generato dopo che la finestra è chiusa si chiamerà Chiusa.

Non utilizzare prefissi prima o dopo o suffissi per indicare eventi pre e post .

Do nome gestori di eventi (delegati utilizzati come tipi di eventi) con il suffisso EventHandler .

Utilizzare due parametri denominati mittente e e nelle firme del gestore eventi.

Il parametro mittente deve essere di tipo oggetto, e il parametro e dovrebbe essere un'istanza o ereditare da EventArgs.

Do nome classi argomento evento con il suffisso EventArgs.

12

Direi che la migliore guida per gli eventi in generale, comprese le convenzioni di denominazione, è here.

E 'la convenzione che ho adottato, brevemente:

  • Eventi nomi sono in genere terminano con un verbo che termina con -ing o -ed (chiusura/Chiuso, carico/Loaded)
  • La classe che dichiara che l'evento dovrebbe avere un On [EventName] virtuale protetto che dovrebbe essere utilizzato dal resto della classe per aumentare l'evento. Questo metodo può essere utilizzato anche da sottoclassi per aumentare l'evento e anche sovraccaricato per modificare la logica di innalzamento degli eventi.
  • C'è spesso confusione sull'uso di "Gestore": per coerenza, tutti i delegati devono essere postati con Gestore, cercare di evitare di chiamare i metodi che implementano i gestori "Gestore"
  • La convenzione di denominazione VS predefinita per il metodo che implementa il gestore è EventPublisherName_EventName.
2

Nel tuo caso potrebbe essere:

class Metronome { 
    event Action Ticked; 

    internalMethod() { 
    // bla bla 
    Ticked(); 
    } 
} 

Sopra uso sampple sotto convenzione, autodescrittivi;]

Avvenimenti Fonte:

class Door { 

    // case1: property change, pattern: xxxChanged 
    public event Action<bool> LockStateChanged; 

    // case2: pure action, pattern: "past verb" 
    public event Action<bool> Opened; 

    internalMethodGeneratingEvents() { 
    // bla bla ... 

    Opened(true); 
    LockStateChanged(false); 
    } 

} 

BTW. parola chiave event è facoltativo ma permette 'eventi' di distinzione da 'callback'

Eventi ascoltatore:

class AlarmManager { 

    // pattern: NotifyXxx 
    public NotifyLockStateChanged(bool state) { 
    // ... 
    } 

    // pattern: [as above]  
    public NotifyOpened(bool opened) { 
    // OR 
    public NotifyDoorOpened(bool opened) { 
    // ... 
    } 

} 

E vincolante [codice è comprensibile all'uomo]

door.LockStateChanged += alarmManager.NotifyLockStateChanged; 
door.Moved += alarmManager.NotifyDoorOpened; 

Anche l'invio manuale di eventi è "umana leggibile".

alarmManager.NotifyDoorOpened(true); 

volte più espressivo può essere "verbo + ing"

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting; 

Qualunque sia la convenzione che si sceglie, essere coerenti con esso.

Problemi correlati