2008-10-31 14 views
27

En siguiente código, quiero extender el comportamiento de una clase derivando/subclasificación de ella, y hacer uso de un evento de la clase base:¿Por qué los eventos no se pueden usar de la misma manera en las clases derivadas que en la clase base en C#?

public class A 
{ 
    public event EventHandler SomeEvent; 

    public void someMethod() 
    { 
     if(SomeEvent != null) SomeEvent(this, someArgs); 
    } 
} 

public class B : A 
{ 
    public void someOtherMethod() 
    { 
     if(SomeEvent != null) SomeEvent(this, someArgs); // << why is this not possible? 
//Error: The event 'SomeEvent' can only appear on the left hand side of += or -= 
//(except when used from within the type 'A') 
    } 
} 

Por qué no es posible?

¿Y cuál es la solución común para este tipo de situación?

Respuesta

29

La práctica estándar aquí es tener un método virtual protegido OnSomeEvent en su clase base, luego llame a ese método en las clases derivadas. Además, por razones de enhebrado, querrá mantener una referencia al manejador antes de comprobar nulo y llamarlo.

Para obtener una explicación de por qué lea Jon Skeet's respuesta o C# specification que describe cómo el compilador crea automáticamente un campo privado.

Aquí hay una posible solución alternativa.

public class A 
{ 
    public event EventHandler SomeEvent; 

    public void someMethod() 
    { 
     OnSomeEvent(); 
    } 

    protected void OnSomeEvent() 
    { 
     EventHandler handler = SomeEvent; 
     if(handler != null) 
      handler(this, someArgs); 
    } 
} 

public class B : A 
{ 
    public void someOtherMethod() 
    { 
     OnSomeEvent(); 
    } 
} 

Editar: código actualizados basados ​​en Framework Design Guidelines section 5.4 y reminders por otros.

+0

No utilice el prefijo '' Levante ..., siempre debería ser '' En .... Además, el método de la clase base debe ser virtual. – Keith

+0

Gracias también por la pista para multihilo. –

+0

No creo que esta sea una buena respuesta porque no aborda la situación en la que la función externa que está delegando tanto en someOtherMethod como en algún método para recibir los mismos parámetros que algunosMethod o someOtherMethod puede tener. en el caso de ObjectDataSource de ASP, si especifica la opción OnSelecting, pasa OnSeleccionar algunos objetos, Object Sender y ObjectDataSourceSelectingEventArgs e. Ahora, en el caso de que haya llenado previamente el ODSSEA con un parámetro que está pasando a otras funciones, este ejemplo es inútil. No se puede pensar en una solución ... –

1

Envuélvanlo con un método virtual ... En protegida:

public class BaseClass 
{ 
    public event EventHandler<MyArgs> SomeEvent; 

    protected virtual void OnSomeEvent() 
    { 
     if(SomeEvent!= null) 
      SomeEvent(this, new MyArgs(...)); 
    } 
} 

Entonces anular este en una clase derivada

public class DerivedClass : BaseClass 
{ 
    protected override void OnSomeEvent() 
    { 
     //do something 

     base.OnSomeEvent(); 
    } 
} 

Vamos a establecer este patrón en todo .Net - todas las formas y los controles web lo siguen.

No use el prefijo Aumentar ... - esto no es coherente con los estándares de MS y puede causar confusión en otros lugares.

40

Otros han explicado cómo solucionar el problema, pero no por qué está por venir.

Cuando declara un evento público de tipo campo, el compilador crea un evento público y un campo privado. Dentro de la misma clase (o clases anidadas) puede acceder al campo directamente, p. invocar a todos los controladores. De otras clases, solo verá el evento, que solo permite la suscripción y la baja.

+0

Muy interesante. ¿tiene un enlace a la documentación relevante? – biozinc

+1

+1, buen detalle de las complejidades. –

+1

No documentación como tal, pero http://pobox.com/~skeet/csharp/events.html contiene más información sobre eventos y delegados en general. –

5

La respuesta de Todd es correcta. A menudo verá esta implementado a lo largo del marco .NET como OnXXX(EventArgs) métodos:

public class Foo 
{ 
    public event EventHandler Click; 

    protected virtual void OnClick(EventArgs e) 
    { 
     var click = Click; 
     if (click != null) 
      click(this, e); 
    } 
} 

le animo fuertemente a considerar la EventArgs<T>/EventHandler<T> pattern antes de que se encuentra haciendo todo tipo de CustomEventArgs/CustomEventHandler para eventos de recaudación.

2

La razón por la que el código original no funciona es porque necesita tener acceso al delegado del evento para poder subirlo, y C# mantiene este delegado private.

Los eventos en C# se representan públicamente por un par de métodos, add_SomeEvent y remove_SomeEvent, por lo que puede suscribirse a un evento desde fuera de la clase, pero no subirlo.

2

Mi respuesta sería que no debería tener que hacer esto.

C# muy bien impone Solo el tipo que declara/publica el evento debe dispararlo/elevarlo. Si la clase base confiaba en que las derivaciones tendrían la capacidad de generar sus eventos, el creador expondría los métodos protegidos para hacerlo. Si no existen, es una buena pista de que probablemente no deberías hacer esto.

Mi ejemplo inventado de cuán diferente sería el mundo si se permitiera a los tipos derivados generar eventos en sus antepasados. Nota: esta es no válida de código C# .. (todavía ..)

public class GoodVigilante 
{ 
    public event EventHandler LaunchMissiles; 

    public void Evaluate() 
    { 
    Action a = DetermineCourseOfAction(); // method that evaluates every possible 
// non-violent solution before resorting to 'Unleashing the fury' 

    if (null != a) 
    { a.Do(); } 
    else 
    { if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty); } 
    } 

    virtual protected string WhatsTheTime() 
    { return DateTime.Now.ToString(); } 
    .... 
} 
public class TriggerHappy : GoodVigilante 
{ 
    protected override string WhatsTheTime() 
    { 
    if (null != LaunchMissiles) LaunchMissiles(this, EventArgs.Empty); 
    } 

} 

// client code 
GoodVigilante a = new GoodVigilante(); 
a.LaunchMissiles += new EventHandler(FireAway); 
GoodVigilante b = new TriggerHappy();    // rogue/imposter 
b.LaunchMissiles += new EventHandler(FireAway); 

private void FireAway(object sender, EventArgs e) 
{ 
    // nuke 'em 
} 
Cuestiones relacionadas