[英]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.