當你自訂一個路由事件的EventArgs,直接繼承RoutedEventArgs,然後新增成員就OK了?不!記住改寫RoutedEventArgs.InvokeEventHandler方法。這個方法是protected virtual。它的預設執行對於一個自訂的RoutedEventArgs衍生類別是很沒效率的!在細說RoutedEventArgs.InvokeEventHandler預設執行之前,先說說為什麼RoutedEventArgs.InvokeEventHandler方法很重要。原因很簡單,因為WPF路由事件的運行將會調用RoutedEventArgs.InvokeEventHandler方法,所以這個方法的效率直接影響到路由事件的最初執行效率。
WPF的執行路由事件方法是UIElement.RaiseEvent,這個方法實際上來自繼承的介面IInputElement。如果你用ILSpy細細追究UIElement.RaiseEvent方法執行,最終會發現如下路徑:
UIElement.RaiseEvent
UIElement.RaiseEventImpl
EventRoute.InvokeHandlers
EventRoute.InvokeHandlersImpl
RoutedItem.InvokeHandler
RoutedEventHanderInfo.InvokeHandler
RoutedEventArgs.InvokeHandler
RoutedEventArgs.InvokeEventHandler
最終會到RoutedEventArgs.InvokeEventHandler方法中。
接下來看看RoutedEventArgs.InvokeEventHandler的預設執行:
這是它的原始碼:
// System.Windows.RoutedEventArgs
protected virtual void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{
if (genericHandler == null)
{
throw new ArgumentNullException("genericHandler");
}
if (genericTarget == null)
{
throw new ArgumentNullException("genericTarget");
}
if (this._routedEvent == null)
{
throw new InvalidOperationException(SR.Get("RoutedEventArgsMustHaveRoutedEvent"));
}
this.InvokingHandler = true;
try
{
if (genericHandler is RoutedEventHandler)
{
((RoutedEventHandler)genericHandler)(genericTarget, this);
}
else
{
genericHandler.DynamicInvoke(new object[]
{
genericTarget,
this
});
}
}
finally
{
this.InvokingHandler = false;
}
}
可以看到,如果目標委託類型是RoutedEventHandler,那麼直接調用目標委託,這個是沒有效率損耗的。但如果不是,則通過Delegate.DynamicInvoke這種反射方式來調用委託,這個執行是有效率損耗的。很顯然,對於RoutedEventArgs,它屬於RoutedEventHandler的參數,而自訂的RoutedEventArgs是肯定不屬於RoutedEventHandler類型的(參數類型不符),那麼最終執行預設會用反射調用委託。
所以記住每次自訂RoutedEventArgs,手動改寫InvokeEventHandler,讓它更有效率:
//自訂RoutedEventArgs
class MyRoutedEventArgs : RoutedEventArgs
{
//手動改寫RoutedEventArgs.InvokeEventHandler
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
{
var handler = (EventHandler<MyRoutedEventArgs>)genericHandler;
handler(genericTarget, this);
}
}