2009-05-07 13 views
5

Supongamos que tengo un método que cambia el estado de un objeto, y dispara un evento para notificar a los oyentes de este cambio de estado:¿Puedo tener una fuerte excepción de seguridad y eventos?

public class Example 
{ 
    public int Counter { get; private set; } 

    public void IncreaseCounter() 
    { 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } 

    protected virtual void OnCounterChanged(EventArgs args) 
    { 
     if (CounterChanged != null) 
     CounterChanged(this,args); 
    } 

    public event EventHandler CounterChanged; 
} 

Los controladores de eventos pueden lanzar una excepción, incluso si IncreaseCounter completado con éxito el cambio de estado. Así que no tenemos una fuerte exception safety aquí:

La garantía fuerte: que la operación ha completado con éxito, ya sea o iniciado una excepción, dejando el estado del programa exactamente como imperante antes del inicio de la operación.

¿Es posible tener una fuerte excepción de seguridad cuando necesita eventos?

Respuesta

3

Para evitar una excepción en un manejador se propaguen al generador de eventos, la respuesta es invocar manualmente cada elemento en el Delegado MultiCast (es decir, el controlador de eventos) dentro de un try-catch

Se llamarán todos los manejadores, y la excepción w no propagar

public EventHandler<EventArgs> SomeEvent; 

protected void OnSomeEvent(EventArgs args) 
{ 
    var handler = SomeEvent; 
    if (handler != null) 
    { 
     foreach (EventHandler<EventArgs> item in handler.GetInvocationList()) 
     { 
      try 
      { 
       item(this, args); 
      } 
      catch (Exception e) 
      { 
       // handle/report/ignore exception 
      } 
     }     
    } 
} 

lo que queda es para que usted pueda implementar la lógica de lo que hagas cuando uno o más destinatarios de eventos tiros y los otros no lo hacen. El catch() también podría detectar una excepción específica y revertir cualquier cambio si eso es lo que tiene sentido, permitiendo que el destinatario del evento señale al origen del evento que se ha producido una situación excepcional.

Como otros señalan, no se recomienda utilizar excepciones como flujo de control. Si es realmente una circunstancia excepcional, entonces use una excepción. Si recibe muchas excepciones, probablemente quiera usar otra cosa.

+0

En otras palabras, lograr una fuerte excepción de seguridad en los métodos que generan eventos tiene el costo de tener que tragar/registrar excepciones. Eso casi siempre es algo malo, así que llegué a la conclusión de que lograr una fuerte excepción de seguridad probablemente no valga la pena. –

0

¿No cambiará simplemente el orden de las dos líneas en IncreaseCounter enforcestrong excepción de seguridad para usted?

O si lo necesidad para incrementar Counter antes de levantar el evento:

public void IncreaseCounter() 
{ 
    this.Counter += 1; 

    try 
    { 
     OnCounterChanged(EventArgs.Empty); 
    } 
    catch 
    { 
     this.Counter -= 1; 

     throw; 
    } 
} 

En una situación en la que usted tiene cambios de estado más complejas (efectos secundarios) en marcha, entonces se convierte en algo más complicada (es posible que tenga que clonar ciertos objetos, por ejemplo), pero para este ejemplo, parecería bastante fácil.

+0

el único problema con esto es que el valor del contador será como era antes de que se incrementara (que no sería el valor esperado para la mayoría de los desarrolladores) –

+0

@ Alan: Sí, lo consideré. Ver mi versión editada – Noldorin

+0

Además, no te olvides de propagar la excepción. Actualmente, esto no se completa con éxito o genera un error para la persona que llama. – workmad3

2

El patrón general verá los marcos es:

public class Example 
{ 
    public int Counter { get; private set; } 

    public void IncreaseCounter() 
    { 
     OnCounterChanging(EventArgs.Empty); 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } 

    protected virtual void OnCounterChanged(EventArgs args) 
    { 
     if (CounterChanged != null) 
     CounterChanged(this, args); 
    } 

    protected virtual void OnCounterChanging(EventArgs args) 
    { 
     if (CounterChanging != null) 
     CounterChanging(this, args); 
    } 

    public event EventHandler<EventArgs> CounterChanging; 
    public event EventHandler<EventArgs> CounterChanged; 
} 

Si un usuario desea lanzar una excepción para evitar el cambio de valor, entonces debe hacerlo en el OnCounterChanging() evento en vez del OnCounterChanged(). Por definición del nombre (tiempo pasado, sufijo de -ed) que implica que el valor ha sido cambiado.

Editar:

Tenga en cuenta que por lo general desea permanecer lejos de grandes cantidades de try..catch extra/bloques finally como controladores de excepciones (incluyendo try..finally) son caros dependiendo de la implementación del lenguaje. es decir, el modelo enmarcado win32 o el modelo de excepción mapeado en PC tienen sus pros y sus contras; sin embargo, si hay demasiados marcos, ambos serán costosos (ya sea en espacio o velocidad de ejecución). Simplemente otra cosa a tener en cuenta al crear un marco.

+2

Esto no sería mejor para garantizar una seguridad de excepción fuerte que una convención que dice 'no arrojar excepciones en los controladores de eventos'. En realidad, no impide que alguien rompa el sistema lanzando excepciones donde no deberían. Si eso es realmente un problema o no es diferente :) – workmad3

+0

Las excepciones son un mecanismo útil, y el costo de prueba/captura es mínimo cuando no hay excepciones. Nunca se deben usar excepciones para el flujo de control normal, pero no creo que eso sea lo que el OP está pidiendo. –

+0

try..catch El costo es mínimo para las excepciones basadas en el marco de la pila cuando no hay excepciones. Las excepciones de mapas de PC tienen un costo de tamaño asociado, por lo que cantidades copiosas de estos tipos de manejadores de excepciones podrían comenzar a inflar su DS. –

0

En este caso, creo que debería seguir el ejemplo de la Fundación Workflow. En el método OnCounterChanged, rodee la llamada al delegado con un bloque genérico try-catch. En el controlador de capturas, tendrás que hacer lo que efectivamente es una acción de compensación: retroceder el contador.

1

Bueno, se podría compensar si era crítico:

public void IncreaseCounter() 
{ 
    int oldCounter = this.Counter; 
    try { 
     this.Counter = this.Counter + 1; 
     OnCounterChanged(EventArgs.Empty); 
    } catch { 
     this.Counter = oldCounter; 
     throw; 
    } 
} 

Obviamente, esto sería mejor si se puede hablar con el campo (ya que la asignación de un campo no lo hará de error).También podría terminar con esto en bolierplate:

void SetField<T>(ref T field, T value, EventHandler handler) { 
    T oldValue = field; 
    try { 
     field = value; 
     if(handler != null) handler(this, EventArgs.Empty); 
    } catch { 
     field = oldField; 
     throw; 
    } 
} 

y llame:

SetField(ref counter, counter + 1, CounterChanged); 

o algo similar ...

+0

He considerado la solución de reversión, pero si hay controladores de eventos que ya manejaron con éxito el cambio, entonces tendrían que saber que se ha retrotraído. Entonces tendría que plantear el evento nuevamente para la reversión, pero luego podría lanzarse la misma excepción nuevamente, etc. –

Cuestiones relacionadas