2009-04-07 33 views
52

Estoy aprendiendo sobre eventos/delegados en C#. ¿Podría preguntar su opinión sobre el estilo de nomenclatura/codificación que he elegido (tomado del libro de Head First C#)?Eventos: convención y estilo de nomenclatura

Estoy enseñando a un amigo sobre esto mañana, y estoy tratando de encontrar la forma más elegante de explicar los conceptos. (Pensó que la mejor manera de entender un tema es tratar de enseñar!)

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. El código se refactoriza desde http://www.codeproject.com/KB/cs/simplesteventexample.aspx

Respuesta

46

Hay algunos puntos que me gustaría mencionar: no parece

Metronome.OnTick ser identificado correctamente. Semánticamente, "OnTick" me dice que se llamará cuando marque "Tic", pero eso no es realmente lo que está sucediendo. Yo lo llamaría "ir" en su lugar.

El modelo generalmente aceptado, sin embargo, sería hacer lo siguiente. OnTick es un método virtual que plantea el evento. De esta forma, puede anular fácilmente el comportamiento predeterminado en clases heredadas y llamar a la base para plantear el 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 
     } 
    } 
} 

Además, sé que esto es un ejemplo sencillo, pero si no hay oyentes conectados al mismo, su código tirará en Tick(this, EventArgs.Empty). Usted debe incluir al menos un guardia nula para comprobar si los oyentes:

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

Sin embargo, esto sigue siendo vulnerable en un entorno multiproceso si el oyente no está registrado entre la protección y la invocación. Lo mejor sería para capturar los oyentes actuales primero y llamarlos:

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

Sé que esto es una respuesta de edad, pero ya que todavía está reuniendo upvotes, esto es la forma en C# 6 de hacer las cosas. Todo el concepto "guardia" puede ser sustituida por una llamada al método condicional y el compilador de hecho hacer lo correcto (TM) en lo que respecta a la captura de los oyentes:

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

Alternativa al guardia es agregar "= delegar {};" a la declaración Tick (ver http://stackoverflow.com/questions/231525/raising-c-events-with-an-extension-method-is-it-bad/231536#231536) – Benjol

+0

Tenga en cuenta que la vulnerabilidad no es _limitada_ a entornos multiproceso. Es posible (si es sociópata) que un manejador de eventos elimine todos los manejadores de un evento, lo que da como resultado un bloqueo cuando el manejador finaliza y la invocación de evento intenta ejecutar el siguiente evento (ahora inexistente). –

+0

@GregD: ¿Hay alguna manera de ponerlo a prueba de manera que el código del cliente no pueda hacer eso? –

2

Se ve bien, aparte del hecho de que OnTick no sigue el modelo típico de invocación de eventos. Por lo general, On[EventName] genera el evento de una sola vez, como

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

Considerar la creación de este método, y cambiar el nombre del método existente "OnTick" a "StartTick", y en lugar de invocar Tick directamente de StartTick, llame OnTick(EventArgs.Empty) del StartTick método.

4

Un punto que he encontrado después de usar eventos en. Net durante muchos años es la necesidad repetitiva de verificar el evento para un controlador nulo en cada invocación. Todavía tengo que ver un código en vivo que haga cualquier cosa, pero no llame al evento si es nulo.

Lo que he comenzado a hacer es poner un manejador ficticio en cada evento que creo para salvar la necesidad de hacer la comprobación nula.

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 para señalar es que esto no funciona con la serialización. La idea del delegado vacío se analiza detenidamente en http://stackoverflow.com/questions/9033/hidden-features-of-c/9282#9282 –

+1

Puede ser aún más fácil: 'public event EventHandler Tick = delegate {};' – Mikhail

56

Microsoft ha escrito un conjunto extenso de directrices de nomenclatura y lo colocó en la biblioteca de MSDN. Puede encontrar los artículos aquí: Guidelines for Names

Aparte de las directrices generales de capitalización, esto es lo que tiene para 'Eventos' en la página Names of Type Members:

hacer Actos nombre con un verbo o una frase verbal .

Dale a los nombres de eventos un concepto de antes y después, usando el presente y tiempo pasado. Por ejemplo, un evento de cierre que se produce antes de que se cierre una ventana se llamaría Cierre y uno que se genera después de que la ventana se cerró se llamaría Cerrado.

No utilice los prefijos Antes o Después o sufijos para indicar pre y post eventos.

Escriba el nombre de los controladores de eventos (los delegados utilizaron como tipos de eventos) con el sufijo EventHandler .

Utilice dos parámetros denominados sender y e en las firmas del controlador de eventos.

El parámetro remitente debe ser de tipo objeto, y el parámetro e debe ser una instancia de o heredar de EventArgs.

Escriba las clases de argumento de evento con el sufijo EventArgs.

12

Diría que la mejor guía para eventos en general, incluidas las convenciones de nomenclatura, es here.

Es la convención que he adoptado, en pocas palabras:

  • Eventos nombres son típicamente terminan con un verbo que termina con -ing o -ed (Cierre/cerrado, cargando/Cargado)
  • La clase que declara que el evento debe tener un virtual protegido en [EventName] que debería ser utilizado por el resto de la clase para generar el evento. Este método también puede ser utilizado por subclases para plantear el evento, y también sobrecargado para modificar la lógica de generación de eventos.
  • A menudo hay confusión sobre el uso de 'Handler' - de coherencia, todos los delegados deben ser fijaron posteriormente con Handler, tratar de evitar llamar a los métodos que implementan 'manipuladores' El manejador
  • El valor por defecto VS convención de nombres para el método que implementa el controlador es EventPublisherName_EventName.
2

En su caso podría ser:

class Metronome { 
    event Action Ticked; 

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

Por encima de uso sampple continuación convención, la auto-descripción;]

Eventos Fuente:

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. palabra clave event es opcional, pero permite a los 'eventos' distintivas de

Eventos oyente devoluciones de llamada ':

class AlarmManager { 

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

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

} 

Y vinculante [código es amigable humana]

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

Incluso el envío de eventos manualmente es "humana legible".

alarmManager.NotifyDoorOpened(true); 

veces más expresivo puede ser "verbo + ing"

dataGenerator.DataWaiting += dataGenerator.NotifyDataWaiting; 

Cualquiera que sea la convención que elija, ser coherente con ella.

Cuestiones relacionadas