2009-06-18 5 views
39

I han declarado un controlador de eventos genéricoC#: evento con explicity add/remove! = ¿Evento típico?

public delegate void EventHandler(); 

a la que he añadido la extensión método 'RaiseEvent':

public static void RaiseEvent(this EventHandler self)  { 
    if (self != null) self.Invoke(); 
} 

Cuando definir el evento utilizando la sintaxis típica

public event EventHandler TypicalEvent; 

luego puedo llamar usando el método de extensión sin problemas:

TypicalEvent.RaiseEvent(); 

Pero cuando defino el evento con complemento explícito/eliminar la sintaxis

private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent { 
    add { _explicitEvent += value; } 
    remove { _explicitEvent -= value; } 
} 

entonces el método de extensión no existe en el caso definido con el complemento explícito/eliminar sintaxis:

ExplicitEvent.RaiseEvent(); //RaiseEvent() does not exist on the event for some reason 

Y cuando me cierne sobre el evento para ver el motivo dice:

El evento 'ExplicitEvent' solo puede aparecen en el lado izquierdo de + = o - =

¿Por qué un evento define utilizando la sintaxis típica ser diferente de un evento definido utilizando el complemento explícito/eliminar la sintaxis y por qué los métodos de extensión no funcionan en este último?

EDIT: He encontrado que puedo trabajar alrededor de ella utilizando el controlador de eventos privados directamente:

_explicitEvent.RaiseEvent(); 

Pero todavía no entiendo por qué no puedo utilizar el evento directamente como el evento definido usando la sintaxis típica. Tal vez alguien me puede iluminar.

+0

Tampoco puede usar el método de extensión desde fuera de la clase. – IllidanS4

Respuesta

34

Debido a que usted puede hacer esto (que es muestra no en el mundo real, pero "funciona"):

private EventHandler _explicitEvent_A; 
private EventHandler _explicitEvent_B; 
private bool flag; 
public event EventHandler ExplicitEvent { 
    add { 
     if (flag = !flag) { _explicitEvent_A += value; /* or do anything else */ } 
     else { _explicitEvent_B += value; /* or do anything else */ } 
    } 
    remove { 
     if (flag = !flag) { _explicitEvent_A -= value; /* or do anything else */ } 
     else { _explicitEvent_B -= value; /* or do anything else */ } 
    } 
} 

¿Cómo puede el compilador saber de qué se debe hacer con "ExplicitEvent.RaiseEvent();" ? Respuesta: No puede.

El "ExplicitEvent.RaiseEvent();" es solo azúcar de sintaxis, que solo puede predicarse si el evento se implementa implícitamente.

+7

¿Cómo sabes que el compilador es un "He"? Es broma, gracias por la respuesta y la muestra que lo explica muy bien. Me encanta este sitio para obtener respuestas rápidas como esta. –

+0

+1 lo más rápido. Es lo que me hizo regresar. Eso y ... la inmensa cantidad de conocimiento. –

1

La declaración "simple" para TypicalEvent hace algunos trucos de compilación. Crea una entrada de metadatos de evento, agrega y elimina métodos y un campo de respaldo. Cuando su código se refiere a TypicalEvent, el compilador lo traduce en una referencia al campo de respaldo; cuando el código externo se refiere a TypicalEvent (usando + = y - =), el compilador lo traduce en una referencia al método add o remove.

La declaración "explícita" omite este truco del compilador. Está explicando los métodos para agregar y eliminar y el campo de respaldo: de hecho, como lo señala TcKs, es posible que no sea un campo de respaldo (esta es una razón común para usar el formulario explícito: ver, por ejemplo, eventos en System.Windows .Forms.Control).Por lo tanto, el compilador puede traducir ya no en silencio la referencia a TypicalEvent en una referencia al campo respaldo: si desea que el campo de respaldo, el objeto delegado real, hay que hacer referencia al campo de respaldo directamente:

_explicitEvent.RaiseEvent() 
+2

No del todo - dentro de la clase, + = y - = will * still * se refieren al campo. Se refiere al evento cuando lo llamas desde fuera de la clase. –

+0

Gracias por su respuesta. Lo explicaste muy bien. Ya encontré la solución por mí mismo, pero aún así agradezco la muestra sobre cómo llamaría a RaisEvent() en el evento explícito. –

+0

Jon, gracias por la información. Siempre es bueno recordar las complejidades del uso de eventos. –

49

Cuando crear un evento "-campo como", así:

public event EventHandler Foo; 

el compilador genera un campo y un evento. Dentro del código fuente de la clase que declara el evento, cada vez que se refiera a Foo, el compilador comprenderá que se está refiriendo al campo . Sin embargo, el campo es privado, por lo que cada vez que se refiera a Foo de otras clases de, se refiere al evento (y, por lo tanto, al código de agregar/quitar).

Si declara su propio código explícito de agregar/eliminar, no obtiene un campo generado automáticamente. Por lo tanto, solo tiene un evento y no puede generar un evento directamente en C#; solo puede invocar una instancia de delegado. Un evento no es una instancia de delegado, es solo un par de agregar/quitar.

Ahora su código contenía esto:

public EventHandler TypicalEvent; 

Ésta es ligeramente diferente todavía - no estaba declarando un evento en todos - se declara un campo público del tipo de delegado EventHandler. Cualquiera puede invocar eso, porque el valor es solo una instancia de delegado. Es importante entender la diferencia entre un campo y un evento. Nunca debe escribir este tipo de código, del mismo modo que estoy seguro de que normalmente no tiene campos públicos de otros tipos, como string y int. Desafortunadamente es un error fácil de escribir, y uno relativamente difícil de detener. Solo lo detectarías al notar que el compilador te permitía asignar o usar el valor de otra clase.

Consulte mi article on events and delegates para obtener más información.

+0

Gracias Jon, ya leí tu artículo hace un tiempo, simplemente no estaba inmediatamente claro para mí cuando noté este "comportamiento extraño" (para mí en ese momento) con el método de extensión. –

+0

Así que el evento actúa como propiedad. – Alex78191

+0

@ Alex78191: Bueno, tipo de - excepto con agregar/eliminar en lugar de get/set. –

7

Eso es porque no lo estás mirando bien. La lógica es la misma que en Propiedades. Una vez que haya configurado la opción de agregar/eliminar, ya no se trata de un evento real, sino de un contenedor para exponer el evento real (los eventos solo se pueden desencadenar desde la propia clase, por lo que siempre tendrá acceso local al evento real).

private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent { 
    add { _explicitEvent += value; } 
    remove { _explicitEvent -= value; } 
} 

private double seconds; 
public double Hours 
{ 
    get { return seconds/3600; } 
    set { seconds = value * 3600; } 
} 

En ambos casos, el miembro con la propiedad get/set o add/remove realmente no contiene ningún dato. Necesita un miembro privado "real" para contener los datos reales. Las propiedades solo le permiten programar una lógica extra al exponer a los miembros al mundo exterior.

Un buen ejemplo de por qué quieres hacerlo, es detener el cómputo adicional cuando no es necesario (nadie está escuchando el evento).

Por ejemplo, digamos que los eventos se desencadenan por un temporizador, y no queremos que el tiempo funcione si nadie se registrará en el evento:

private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); 
private EventHandler _explicitEvent; 
public event EventHandler ExplicitEvent 
{ 
    add 
    { 
     if (_explicitEvent == null) timer.Start(); 
     _explicitEvent += value; 
    } 
    remove 
    { 
     _explicitEvent -= value; 
     if (_explicitEvent == null) timer.Stop(); 
    } 
} 

Usted probablemente querrá bloquear el agregar/eliminar con un objeto (una idea de último momento) ...

Cuestiones relacionadas