2008-10-16 12 views
12

Algunos textos antes del código para que el resumen de la pregunta no se trunque.¿Por qué alguien debe suscribirse para que ocurra un evento?

class Tree 
{ 
    public event EventHandler MadeSound; 

    public void Fall() { MadeSound(this, new EventArgs()); } 

    static void Main(string[] args) 
    { 
     Tree oaky = new Tree(); 
     oaky.Fall(); 
    } 
} 

No he utilizado mucho los eventos en C#, pero el hecho de que esto causaría un NullRefEx parece raro. La referencia de EventHandler se considera nula porque actualmente no tiene suscriptores, pero eso no significa que el evento no se haya producido, ¿verdad?

EventHandlers se diferencian de los delegados estándar por el evento palabra clave. ¿Por qué los diseñadores de idiomas no los prepararon para disparar silenciosamente al vacío cuando no tenían suscriptores? (Supongo que puedes hacerlo manualmente agregando explícitamente un delegado vacío).

+1

Ejemplo impresionante! –

+0

También +1 para el ejemplo. Supongo que llamar a Fall en un bosque solitario no disparará a MadeSound. – OregonGhost

+0

cuando un árbol cae en el bosque y nadie está escuchando, sigue emitiendo un sonido - dice "Moo" –

Respuesta

3

Necesita entender lo que realmente está haciendo su declaración de evento. Está declarando tanto un evento como una variable. Cuando te refieres a él dentro de la clase, solo te estás refiriendo a la variable, que será nula cuando no haya suscriptores.

+0

No creo que se esté refiriendo a la variable, ya que C# no permitirá que una clase uno manipule sus eventos en la forma en que manipula las variables.Por ejemplo, C# permitirá "someEvent + = someDeletate;", pero no creo que permita "someEvent = someEvent + someDelegate;". Dado que, estoy desconcertado por qué el uso de un evento de una manera sintácticamente similar a invocar a un delegado no está codificado como "delegar grab e invocar si no es nulo". Por otro lado, si tuviera mi druthers, los eventos usarían un mecanismo diferente de los delegados de multidifusión de todos modos. – supercat

+0

@supercat a C# event es solo un delegado con la palabra clave 'event'. Esto ofrece las restricciones de acceso especiales que describió, pero es solo una clase. – Gusdor

+0

@Gusdor: No, no es * no * "solo un delegado" más que una propiedad es "solo un campo". Un evento es un par de métodos, efectivamente: uno para suscribirse y otro para darse de baja. ¿De qué manera es eso "solo una clase"? –

6

Bueno, la forma canónica es:

void OnMadeSound() 
{ 
    if (MadeSound != null) 
    { 
     MadeSound(this, new EventArgs()); 
    } 
} 

public void Fall() { OnMadeSound(); } 

que es muy poco más rápido que llamar a un delegado vacía, así que la velocidad se impuso sobre la conveniencia de programación.

+0

Creo que se recomienda que el método On * esté protegido para permitir que una clase derivada reaccione al evento o cambie cómo o cuándo se llama al evento. Solo como una adición. – OregonGhost

+0

En realidad, la forma más segura es la siguiente: protected void OnMadeSound() { EventHandler tempHandler = MadeSound; if (tempHandler! = Null) { tempHandler (esto, nuevos EventArgs()); } } –

+0

El método debe estar protegido y el TempHandler evita la posibilidad de que ocurra una NullReferenceException si el controlador se elimina entre la comprobación nula y en realidad se genera el evento. –

3

Very Zen, ¿eh?

usted tiene que probar para nula cuando se quiere generar un evento:

protected void OnMyEvent() 
{ 
    if (this.MyEvent != null) this.MyEvent(this, EventArgs.Empty); 
} 

Sería bueno si usted no tiene que molestarse con esto, sino que es la rompe.

+0

Cool, EventArgs.Empty :) – xyz

2

James proporcionó un buen razonamiento técnico, también me gustaría añadir que he visto a las personas usar esta ventaja, si ningún suscriptor está escuchando un evento, tomarán medidas para ingresarlo en el código o algo similar. Un ejemplo simple, pero apropiado en este contexto.

2

¿De qué sirve organizar un evento si nadie está escuchando? Técnicamente, es cómo C# eligió implementarlo.

En C#, un evento es un delegado con algunas plumas especiales. Un delegado en este caso se puede ver como una lista vinculada de indicadores de función (para manejar métodos de suscriptores). Cuando "enciendes el evento", se invoca cada puntero a la vez. Inicialmente, el delegado es un objeto nulo como cualquier otra cosa. Cuando se hace un + = para la primera acción de suscripción, se llama a Delegate.Combine, que crea una instancia de la lista. (Llamada null.Invoke() arroja la excepción nula - cuando se dispara el evento.)

Si aún sientes que "no debe ser así", utiliza una clase de ayuda EventsHelper como se menciona aquí con el antiguo y mejorado 'evento defensivo la publicación' http://weblogs.asp.net/rosherove/articles/DefensiveEventPublishing.aspx

4

Otra buena manera que he visto de evitar esto, sin tener que recordar para comprobar nulo:

class Tree 
{ 
    public event EventHandler MadeSound = delegate {}; 

    public void Fall() { MadeSound(this, new EventArgs()); } 

    static void Main(string[] args) 
    { 
     Tree oaky = new Tree(); 
     oaky.Fall(); 
    } 
} 

Nota del delegado anónimo - probablemente un pequeño impacto de rendimiento, así que hay que averigüe qué método (verificar nulo o delegado vacío) funciona mejor en su situación.

+0

su código aún puede configurar el evento MadeSound como nulo, por lo que aún puede obtener una NullReferenceException, haciendo que el delegado {} sea inútil –

+0

O, en lugar de establecerlo en nulo, puede configurarlo para delegar {} ... ¿Con qué frecuencia? usted establece un evento nulo? No he tenido que hacerlo todavía. –

0

Gracias por las respuestas.Entiendo por qué ocurre NullReferenceException y cómo evitarlo.

Gishu dijo

¿Cuál es el punto de elevar un evento si nadie está escuchando?

Bueno, tal vez es una cuestión de terminología. El atractivo de un sistema de "evento" me parece que toda la responsabilidad de las consecuencias del evento que tuvo lugar debe recaer en los observadores y no en el artista intérprete o ejecutante.


Tal vez una mejor cosa que hacer es: Si un campo delegado se declara con la palabra clave evento en frente de ella, ¿por qué el compilador traduce todos los casos de:

MadeSound(this, EventArgs.Empty) 

a

if (MadeSound != null) { MadeSound(this, EventArgs.Empty); } 

detrás de escena de la misma manera que otros atajos de sintaxis? El número de métodos de comprobación nula OnSomeEvent repetitivos que las personas deben escribir manualmente debe ser colosal.

+0

El compilador está aislando lo que está haciendo aquí en un solo aspecto: crear una variable y un evento con el mismo nombre. Preferiría que no hiciera más que eso, por el bien de la complejidad. Sin embargo, es fácil evitar la verificación nula: evento público EventHandler Click = delegate {}; –

+0

Por lo tanto, desde fuera de la clase, MadeSound se refiere a un "evento", que gestiona la adición o eliminación de delegados adecuados a una instancia del delegado de EventHandler que comparte el mismo identificador y oculta el "evento" internamente. Es decir, ¿el evento no se dispara directamente, sino la lista de delegados que produjo? – xyz

+0

@Jon No estoy de acuerdo. Le tengo mucho respeto pero no veo por qué defiende el patrón de inicializador delegado {}. –

4

El patrón recomendado es (.NET 2.0+)

public class MyClass 
{ 
    public event EventHandler<EventArgs> MyEvent; // the event 

    // protected to allow subclasses to override what happens when event raised. 
    protected virtual void OnMyEvent(object sender, EventArgs e) 
    { 
     // prevent race condition by copying reference locally 
     EventHandler<EventArgs> localHandler = MyEvent; 
     if (localHandler != null) 
     { 
      localHandler(sender, e); 
     } 
    } 
    public void SomethingThatGeneratesEvent() 
    { 
     OnMyEvent(this, EventArgs.Empty); 
    } 
} 

veo un montón de recomendaciones para un delegado de vacío {} en un inicializador, pero estoy totalmente en desacuerdo con ella. Si sigue el patrón anterior, solo verifica event != null en un solo lugar. El inicializador de delegado vacío {} es un desperdicio porque es una llamada extra por evento, desperdicia memoria y aún puede fallar si MyEvent se estableció en nulo en otro lugar de mi clase.

* Si su clase está sellada, no haría OnMyEvent() virtual.

1

Usar un método de extensión sería útil en este escenario.

public static class EventExtension 
{ 
    public static void RaiseEvent<T>(this EventHandler<T> handler, object obj, T args) where T : EventArgs 
    { 
     if (handler != null) 
     { 
      handler(obj, args); 
     } 
    } 
} 

Se puede utilizar a continuación.

public event EventHandler<YourEventArgs> YourEvent; 
... 
YourEvent.RaiseEvent(this, new YourEventArgs()); 
+0

Gracias. De hecho, llegué a ese enfoque e hice uno para los viejos EventArgs, también :) – xyz

Cuestiones relacionadas