2009-05-07 7 views
5

Angenommen, ich eine Methode, die den Zustand eines Objekts ändert, und löst ein Ereignis-Listener dieser Zustandsänderung mitzuteilen:Kann ich starke Ausnahmesicherheit und Ereignisse haben?

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; 
} 

Die Ereignishandler eine Ausnahme auslösen kann, auch wenn IncreaseCounter erfolgreich die Zustandsänderung abgeschlossen. So haben wir nicht stark haben exception safety hier:

Die starke Garantie: dass der Betrieb entweder abgeschlossen hat erfolgreich oder eine Ausnahme geworfen, den Programmzustand verläßt genau wie es war, bevor der Betrieb gestartet.

Ist es möglich, eine starke Ausnahmesicherheit zu haben, wenn Sie Ereignisse auslösen müssen?

Antwort

3

Um eine Ausbreitung auf den Ereignisgenerator eine Ausnahme in einem Handler zu verhindern, Die Antwort besteht darin, jedes Element im MultiCast-Delegierten (dh den Ereignishandler) innerhalb eines try-catch manuell aufzurufen

Alle Handler werden aufgerufen, und die Ausnahme w fortschreiten.

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 
      } 
     }     
    } 
} 

Was bleibt, ist für Sie die Logik für die umzusetzen, was tun, wenn ein oder mehr Ereignis Empfänger wirft und die andere nicht. Die catch() könnte auch eine bestimmte Ausnahme abfangen und alle Änderungen rückgängig machen, wenn dies sinnvoll ist und der Ereignisempfänger der Ereignisquelle signalisieren kann, dass eine Ausnahmesituation eingetreten ist.

Wie andere darauf hinweisen, wird die Verwendung von Ausnahmen als Kontrollfluss nicht empfohlen. Wenn es wirklich ein außergewöhnlicher Umstand ist, dann verwenden Sie auf jeden Fall eine Ausnahme. Wenn Sie viele Ausnahmen haben, möchten Sie wahrscheinlich etwas anderes verwenden.

+0

Mit anderen Worten, das Erzielen einer starken Ausnahmesicherheit in Methoden, die Ereignisse auslösen, geht auf Kosten von Ausnahmen. Das ist fast immer eine schlechte Sache, daher kam ich zu dem Schluss, dass es wahrscheinlich nicht wert ist, eine starke Ausnahmesicherheit zu erreichen. –

0

Wird nicht einfach die Reihenfolge der beiden Zeilen in IncreaseCounter ausgetauscht, um die Ausnahmesicherheit für Sie zu verstärken?

Oder wenn Sie NotwendigkeitCounter zu erhöhen, bevor Sie das Ereignis auslösen:

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

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

     throw; 
    } 
} 

In einer Situation, wo Sie komplexere Zustandsänderungen (Nebenwirkungen) geht, dann wird es etwas komplizierter (Sie müssen zum Beispiel bestimmte Objekte klonen), aber für dieses Beispiel scheint das ziemlich einfach zu sein.

+0

das einzige Problem mit diesem ist, dass der Wert des Zählers sein wird, wie es war, bevor es inkrementiert wurde (was nicht der erwartete Wert für die meisten Entwickler wäre) –

+0

@Alan: Ja, ich habe gerade darüber nachgedacht. Siehe meine bearbeitete Version. – Noldorin

+0

Vergessen Sie auch nicht, die Ausnahme zu propagieren. Momentan wird dies weder erfolgreich abgeschlossen noch löst es einen Fehler für einen Aufrufer aus. – workmad3

2

Das allgemeine Muster, das Sie für Frameworks sehen ist:

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; 
} 

Wenn ein Benutzer möchte eine Ausnahme werfen, die Änderung des Wertes zu verhindern, dann sollten sie es in der OnCounterChanging tun() Ereignis statt der OnCounterChanged(). Nach Definition des Namens (Imperfekt, Suffix -ed) bedeutet dies, dass der Wert geändert wurde.

Edit:

Beachten Sie, dass Sie in der Regel weg von reichlichen Mengen an zusätzlichen try..catch bleiben wollen/finally Blöcke als Exception-Handler (einschließlich try..finally) sind teuer abhängig von der Sprache Umsetzung. h. Das win32-Stack-gerahmte Modell oder das PC-gemappte Exception-Modell haben beide ihre Vor- und Nachteile, wenn es jedoch zu viele dieser Frames gibt, sind sie beide teuer (entweder im Raum oder in der Ausführungsgeschwindigkeit). Eine weitere Sache, die man beim Erstellen eines Frameworks beachten sollte.

+2

Dies wäre nicht besser, um eine starke Ausnahmesicherheit zu gewährleisten, als eine Konvention, die sagt: "Wirf keine Ausnahmen in den Event-Handlern". Es verhindert nicht wirklich, dass jemand das System bricht, indem es Ausnahmen auslöst, wo sie nicht sollten. Ob das wirklich ein Problem ist oder nicht, ist anders :) – workmad3

+0

Ausnahmen sind ein nützlicher Mechanismus, und die Kosten von try/catch sind minimal, wenn es keine Ausnahmen gibt. Ausnahmen sollten niemals für einen normalen Kontrollfluss verwendet werden, aber ich denke nicht, dass das OP dies verlangt. –

+0

try..Catch Kosten ist minimal für Stack-Frame-basierte Ausnahmen, wenn es keine Ausnahmen gibt. PC-gemappte Ausnahmen sind mit ihnen verbundenen Kosten verbunden, so dass große Mengen dieser Arten von Ausnahmebehandlern Ihren DS aufblähen könnten. –

0

In diesem Fall sollten Sie einen Hinweis von Workflow Foundation nehmen. Umschließen Sie in der OnCounterChanged-Methode den Aufruf an den Delegaten mit einem generischen try-catch-Block. Im Catch-Handler müssen Sie tun, was effektiv eine Ausgleichsaktion ist - den Counter zurückrollen.

1

Nun könnte man kompensieren, wenn es kritisch ist:

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

Offensichtlich wäre dies besser, wenn Sie sich auf das Gebiet sprechen können (da ein Feld zuweisen wird nicht Fehler).Sie können auch diese in bolierplate einpacken:

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; 
    } 
} 

und rufen:

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

oder etwas ähnliches ...

+0

Ich habe die Rollback-Lösung in Betracht gezogen, aber wenn Event-Handler die Änderung bereits erfolgreich durchgeführt haben, müssten sie wissen, dass sie zurückgesetzt wurde. Dann müsste ich das Ereignis erneut für das Rollback auslösen, aber dann könnte die gleiche Ausnahme erneut ausgelöst werden usw. –

Verwandte Themen