# 原則30:選擇重載而不是事件處理器
**By D.S.Qiu**
**尊重他人的勞動,支持原創,轉載請注明出處:[http://dsqiu.iteye.com](http://dsqiu.iteye.com)**
很多 .NET 類提供兩種方式處理系統事件。你可以附加指定一個事件處理器或者重載基類的虛函數。為什么提供兩種方式做同一件事情?因為不同的情況需要不同的解決方案,這就是為什么。在子類中,你總可以重載虛函數。這樣不相關對象就不能使用這個事件處理器。你可以寫的一個漂亮的 Windows Presentation Foundation (WPF) 應用,需要響應鼠標按下事件。在你的 Form 類中,你可以選擇重載 OnMouseDown 方法:
```
public partial class Window1 : Window
{
// other code elided
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
DoMouseThings(e);
base.OnMouseDown(e);
}
}
```
或者,你也可以指定一個事件處理器(需要 C# 和 XAML ):
```
<!-- XAML File -->
<Window x:Class="Item36_OverridesAndEvent.Window1" xmlns= "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" MouseDown="OnMouseDown">
<Grid>
</Grid>
</Window>
// C Sharp file:
public partial class Window1 : Window
{
// other code elided
public Window1()
{
InitializeComponent();
}
private void OnMouseDown(object sender,MouseButtonEventArgs e)
{
DoMouseThings(e);
}
private void DoMouseThings(MouseButtonEventArgs e)
{
throw new NotImplementedException();
}
}
```
第一個方案是更好的。WPF 程序中聲明代碼是重點這得可能令人驚訝。即使這樣,如果邏輯代碼需要用代碼實現,你應該使用虛函數。如果一個事件處理器拋出異常了,在鏈中的其他處理器就不會被調用(查看原則24和25)。通過重載 protected 需函數,你的處理器就可以被先調用。基類的虛函數版本是負責處理協議特殊的事情。這意味著如果你想要事件處理器被調用(并且你總是這樣做的),你必須調用基類的虛函數。在一些少數的情況,你不想要默認的行為,你就不調用基類的版本這樣就沒有處理器被調用。你不能保證所有的事件處理器會被調用因為有些不正確的事件處理器會拋出異常,但是你可以保證你的子類行為是正確的。
使用重載比附加事件處理器更高效。你應該記得原則25講到的事件是一個多播委托。這就使得一個事件源會有多個觀察者。這個事件機制會占用處理器多點時間,因為他必須檢查事件是否有添加事件處理器。如果有,它必須遍歷整個調用隊列,而這個隊列可能包含了任意數量的目標函數。隊列的每個方法都會被調用。檢查是否有事件處理器并在運行時遍歷調用會比只調用一個虛函數話費更多時間。
如果這個還不夠,再查看下上面的兩個例子。哪個更清晰?重載一個虛函數只需要檢查一個函數并且如果維護這個表單只需要修改這個函數。事件機制有兩個地方需要維護:事件處理器函數和關聯事件的代碼。這兩個都會導致事件失敗。一個函數就很更簡單了。
好了,我已經給出了使用重載而不是事件處理器的所有理由。那 .NET 框架設計者確定有必要添加事件處理器么?當然有必要。除了我們,他們太忙以至于不會寫沒有人用的代碼。重載只是在子類使用。其他的類都必須使用事件機制。這意味這在 XAML 文件中定義聲明就可以使用事件處理器。在上面的例子中,你的設計者可能會有鼠標按下的事件的行為。設計者可以創建 XAML 聲明它們的行為。這些行為可以響應表單事件。你可以在代碼中重定義所有行為,但那會花更多時間處理一個事件。這只是將設計者的問題轉移給你。你清楚自己想要設計者為你處理設計工作。更好的方式是創建一個事件然后通過設計工具創建 XMAL 聲明。所以最后,你創建一個新的類并發送事件到表單類。這樣一開始就添加一個事件處理器到表單會更簡單。畢竟,這就是為什么 .NET 框架設計者要在表單添加事件。
事件機制的另外一個理由是事件是在運行時被鏈接的。使用事件會有個更大的靈活性。你可以根據程序的不同狀態鏈接不同的事件處理器。假設你在寫一個畫圖程序。根據程序的不同狀態,鼠標按下可能開始畫線,或者可能是選擇一個對象。當使用者切換模式,你可以切換事件處理器。不同的類,不同的事件處理器,事件的處理依賴于程序的狀態。
最后,使用事件,你可以在一個事件上掛多個事件處理器。再想象剛才的畫圖程序。你可能有多個事件處理器跟鼠標事件掛鉤。第一個是具體的行為。第二個是更新狀態欄或者更新不同命令的可用性。多個行為可以在同一個事件響應。
當你基類有一個函數處理一個事件,重載是更好的方法。這個更容易維護,s即使時間退役也更可能是正確的,并且更高效。保留事件處理器做其他用途。更傾向與重載基類的實現而不是附加事件處理器。
小結:
這個原則就是說 WPF 編程實現事件響應要選擇重載虛函數的方式,而不是事件模式!
歡迎各種不爽,各種噴,寫這個純屬個人愛好,秉持”分享“之德!
有關本書的其他章節翻譯請[點擊查看](/category/297763),轉載請注明出處,尊重原創!
如果您對D.S.Qiu有任何建議或意見可以在文章后面評論,或者發郵件(gd.s.qiu@gmail.com)交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。
轉載請在**文首**注明出處:[http://dsqiu.iteye.com/blog/2087024](/blog/2087024)
更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)
- 第一章 C# 語言習慣
- 原則1:使用 屬性(Poperty)代替可直接訪問的數據成員(Data Member)
- 原則2:偏愛 readonly 而不是 const
- 原則3:選擇 is 或 as 而不是強制類型轉換
- 原則4:使用條件特性(conditional attribute)代替 #if
- 原則5:總是提供 ToString()
- 原則6:理解幾個不同相等概念的關系
- 原則7:明白 GetHashCode() 的陷阱
- 原則8:優先考慮查詢語法(query syntax)而不是循環結構
- 原則9:在你的 API 中避免轉換操作
- 原則10:使用默認參數減少函數的重載
- 原則11:理解小函數的魅力
- 第二章 .NET 資源管理
- 原則12:選擇變量初始化語法(initializer)而不是賦值語句
- 原則13:使用恰當的方式對靜態成員進行初始化
- 原則14:減少重復的初始化邏輯
- 原則15:使用 using 和 try/finally 清理資源
- 原則16:避免創建不需要的對象
- 原則17:實現標準的 Dispose 模式
- 原則17:實現標準的 Dispose 模式
- 原則18:值類型和引用類型的區別
- 原則19:確保0是值類型的一個有效狀態
- 原則20:更傾向于使用不可變原子值類型
- 第三章 用 C# 表達設計
- 原則21:限制你的類型的可見性
- 原則22:選擇定義并實現接口,而不是基類
- 原則23:理解接口方法和虛函數的區別
- 原則24:使用委托來表達回調
- 原則25:實現通知的事件模式
- 原則26:避免返回類的內部對象的引用
- 原則27:總是使你的類型可序列化
- 原則28:創建大粒度的網絡服務 APIs
- 原則29:讓接口支持協變和逆變
- 第四章 和框架一起工作
- 原則30:選擇重載而不是事件處理器
- 原則31:用 IComparable&lt;T&gt; 和 IComparer&lt;T&gt; 實現排序關系
- 原則32:避免 ICloneable
- 原則33:只有基類更新處理才使用 new 修飾符
- 原則34:避免定義在基類的方法的重寫
- 原則35:理解 PLINQ 并行算法的實現
- 原則36:理解 I/O 受限制(Bound)操作 PLINQ 的使用
- 原則37:構造并行算法的異常考量
- 第五章 雜項討論
- 原則38:理解動態(Dynamic)的利與弊
- 原則39:使用動態對泛型類型參數的運行時類型的利用
- 原則40:使用動態接收匿名類型參數