[英]Can I have strong exception safety and events?
假设我有一个方法可以更改对象的状态,并触发一个事件以通知侦听器此状态更改:
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;
}
即使IncreaseCounter
成功完成了状态更改,事件处理程序也可能引发异常。 因此,我们在这里没有强大的异常安全性 :
强有力的保证:操作已成功完成或引发了异常,从而使程序状态与操作开始之前的状态完全相同。
当您需要引发事件时,是否可以具有强大的异常安全性?
为了防止处理程序中的异常传播到事件生成器,答案是在try-catch内手动调用MultiCast委托中的每个项目(即事件处理程序)
所有处理程序都将被调用,并且异常不会传播。
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
}
}
}
}
剩下的就是您实现一个或多个事件接收者抛出而其他事件没有抛出时该做什么的逻辑。 catch()也可以捕获特定的异常,并在合理的情况下回滚所有更改,从而使事件接收者可以向事件源发出信号,表明发生了异常情况。
正如其他人指出的那样,不建议将异常用作控制流。 如果确实是例外情况,则一定要使用例外。 如果您遇到许多异常,则可能要使用其他东西。
您将看到的框架的一般模式是:
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;
}
如果用户想抛出一个异常以防止值更改,那么他们应该在OnCounterChanging()事件而不是OnCounterChanged()事件中进行更改。 通过名称的定义(过去时,-ed的后缀),表示该值已更改。
编辑:
请注意,您通常希望避免大量额外的try..catch / finally块,因为异常处理程序(包括try..finally)的昂贵取决于语言实现。 即,win32堆栈框架模型或PC映射的异常模型各有优缺点,但是,如果这些框架太多,则两者都会很昂贵(无论是空间还是执行速度)。 创建框架时要记住的另一件事。
好吧,如果这很关键,您可以补偿:
public void IncreaseCounter()
{
int oldCounter = this.Counter;
try {
this.Counter = this.Counter + 1;
OnCounterChanged(EventArgs.Empty);
} catch {
this.Counter = oldCounter;
throw;
}
}
显然,如果您可以与该领域进行交流,那会更好(因为分配一个领域不会出错)。 您也可以将其包装在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;
}
}
并致电:
SetField(ref counter, counter + 1, CounterChanged);
或类似的东西
难道不可以简单地在IncreaseCounter
交换两行的顺序来为您实施强大的异常安全性吗?
或者,如果您需要在引发事件之前增加Counter
:
public void IncreaseCounter()
{
this.Counter += 1;
try
{
OnCounterChanged(EventArgs.Empty);
}
catch
{
this.Counter -= 1;
throw;
}
}
在状态变化(副作用)不断发生的情况下,情况变得更加复杂(例如,您可能必须克隆某些对象),但是在此示例中,这似乎很容易。
在这种情况下,我认为您应该从Workflow Foundation中获得启发。 在OnCounterChanged方法中,使用通用try-catch块包围对委托的调用。 在catch处理程序中,您必须执行有效的补偿操作-还原计数器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.