“協變”、“逆變”與Delegate類型轉換
我在發表了《Delegate如何進行類型轉換?》之後又想到了其他一些相關的東西,除了簡單地分析如何通過Emit實現EventHandler的類型轉換之外,還加上關於Delegate“協變”與“逆變”的一些東西,算是對前一篇文章的完善。
目錄
一、從Delegate的“協變”與“逆變”說起
二、EventHandler<TEventArgs>是否換一種定義方式更好?
三、“統一的事件注冊”能否應用於一般形式?
四、通過Emit實現EventHandler的類型轉換
五、最簡單的轉換方式
根據Delegate“協變”與“逆變”的原理,對於兩個具有相同聲明的兩個Delegate(A和B),如果B的所有輸入(輸入參數)類是A的子類或者類型相同,而A的輸出(返回值、輸出參數)類型是B的子類或者類型相同,那麼在B能夠使用的地方A也能夠使用。我們在定義泛型Delegate的時候可以利用C#“協變”與“逆變”,使類型為A對象能夠賦值給類型為B的變量。具體來說,我們需要將輸出定義為協變體(通過out關鍵字),而將輸入定義為逆變體(通過in關鍵字)。比如我們熟悉的Func<T, TResult>的定義:
1: public delegate TResult Func<in T, out TResult>(T arg);
如下麵的代碼片斷所示,自定義類型Bar是Foo的子類,那麼我們就可以將一個Func<Foo, Bar> 對象賦值給Func<Bar, Foo>變量。換言之,Func<Bar, Foo>能夠使用的地方,Func<Foo, Bar> 就可以使用。
1: class Program
2: {
3: static void Main()
4: {
5: Func<Foo, Bar> getBarByFoo = foo => new Bar();
6: Func<Bar, Foo> GetFooByBar = getBarByFoo;
7: }
8: }
9: class Foo { }
10: class Bar : Foo { }
事件(Event)是Delegate一項重要的使用領域,一般情況下事件成員的類型都是EventHandler。如果具有特殊的EventArgs類型,我們傾向於使用泛型的EventHandler<TEventArgs>。EventHandler和EventHandler<TEventArgs>這兩個特殊的Delegate類型定義如下,兩者是沒有任何關係的。
1: public delegate void EventHandler(object sender, EventArgs e);
2: public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
根據Delegate“協變”與“逆變”的原理,對於EventHandler<TEventArgs>,其實應該將作為輸入參數類型的TEventArgs定義成逆變形式,像下麵一樣:
1: public delegate void EventHandler< TEventArgs>(object sender, TEventArgs e) where TEventArgs: EventArgs;
如果是這樣的話,EventHandler<EventArgs>就可以用於處理任意EventHandler<TEventArgs>類型的事件了。假設我們需要注冊一個全局的EventHandler,讓它在某個對象任何一個事件觸發的時候被執行,如果我們能夠保證所有的事件類型都是通過協變形式定義的EventHandler<TEventArgs>,我們可以按照如下的方式對目標對象的所有事件進行注冊:
1: public static class EventRegistry<T>
2: {
3: public static void Register(T target, eventHandler)
4: {
5: foreach (EventInfo eventInfo in typeof(T).GetEvents())
6: {
7: eventInfo.AddEventHandler(target, eventHandler);
8: }
9: }
10: }
假設我們具有如下一個類型Foo,它具有三個事件Bar、Baz和Qux,對應的類型分別是EventHandler<BarEventArgs>、EventHandler<BazEventArgs>和EventHandler<QuxEventArgs>,當方法RaiseEvents執行的時候,被注冊的事件被觸發。
1: public class BarEventArgs : EventArgs
2: { }
3: public class BazEventArgs : EventArgs
4: { }
5: public class QuxEventArgs : EventArgs
6: { }
7:
8: public class Foo
9: {
10: public event Bar;
11: public event Baz;
12: public event Qux;
13:
14: public void RaiseEvents()
15: {
16: if (null != Bar) Bar(this, new BarEventArgs());
17: if (null != Baz) Baz(this, new BazEventArgs());
18: if (null != Qux) Qux(this, new QuxEventArgs());
19: }
20: }
比如現在我們需要在Foo對象的任何一個事件觸發的時候進行相應的日誌記錄,我們隻需要利用上麵定義的EventRegistry<T>對創建的Foo對象進行批量事件注冊。
1: public class Propgram
2: {
3: static void Main()
4: {
5: Foo foo = new Foo();
6: EventRegistry<Foo>.Register(foo, Log);
7: foo.RaiseEvents();
8: }
9: static void Log(object sender, EventArgs e)
10: {
11: Console.WriteLine("{0}: {1}", sender.GetType().Name, e.GetType().Name);
12: }
13: }
原則上講,事件可以是任意類型的Delegate,但是我們使用的事件一般具有如下兩個共同點:
- 不具有返回類型,或者返回類型為void;
- 有且隻有兩個輸入參數,其一個參數類型為Object,第二個類型是EventArgs的子類。
如果事件類型對於得Delegate並沒有采用逆變方式定義,那麼要求我們注冊一個與之類型完全一致的Delegate。如果我們需要將一個EventHandler對象注冊給某個對象任意類型的事件,我們就不得不將注冊的EventHandler轉換成具體事件的類型。如下所示的時借助於類型轉換的EventRegistry<T>的定義,類型轉換通過調用EventHandlerConverter的Convert方法來完成。
1: public static class EventRegistry<T>
2: {
3: public static void Register(T target, EventHandler eventHandler)
4: {
5: foreach (EventInfo eventInfo in typeof(T).GetEvents())
6: {
7: eventInfo.AddEventHandler(target, );
8: }
9: }
10: }
那麼現在我們將Foo類型的三個事件類型定義成普通的Delegage:BarEventHandler、BazEventHandler和QuxEventHandler,實際上我們上麵的代碼依然可以執行。
1: public delegate void (object sender, BarEventArgs e);
2: public delegate void (object sender, BazEventArgs e);
3: public delegate void (object sender, QuxEventArgs e);
4:
5: public class Foo
6: {
7: public event BarEventHandler Bar;
8: public event BazEventHandler Baz;
9: public event QuxEventHandler Qux;
10: }
我們通過Emit的形式實現了這個類型轉換。如下麵的代碼片斷所示,實現在EventHandlerConverter的靜態方法Convert方法中的EventHandler與兼容Delegate類型之間的轉換是通過“Emit”的機製實現,具體的實現邏輯如下麵的代碼片斷所示。IsValidEventHandler方法用於驗證指定的類型是否與EventHandler兼容(按照上麵提及的標準進行驗證),在Convert方法中我們通過Emit的方式創建了一個DynamicMethod 對象,並最終調用CreateDelegate方法將指定的Delegate對象轉換成目標Delegate類型。泛型方法Convert<TDelegate>以強類型的方式指定轉換的目標類型。
1: public static class EventHandlerConverter
2: {
3: public static bool IsValidEventHandler(Type eventHandlerType, out ParameterInfo[] parameters)
4: {
5: Guard.ArgumentNotNull(eventHandlerType, "eventHandlerType");
6: if (!typeof(Delegate).IsAssignableFrom(eventHandlerType))
7: {
8: parameters = new ParameterInfo[0];
9: return false;
10: }
11:
12: MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
13: if (invokeMethod.ReturnType != typeof(void))
14: {
15: parameters = new ParameterInfo[0];
16: return false;
17: }
18: parameters = invokeMethod.GetParameters();
19: if (parameters.Length != 2 || parameters[0].ParameterType != typeof(object))
20: {
21: return false;
22: }
23: if (!typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType))
24: {
25: return false;
26: }
27: return true;
28: }
29:
30: public static Delegate Convert(Delegate eventHandler, Type eventHandlerType)
31: {
32: Guard.ArgumentNotNull(eventHandler, "eventHandler");
33: Guard.ArgumentNotNull(eventHandlerType, "eventHandlerType");
34:
35: ParameterInfo[] destinationParameters;
36: if (!IsValidEventHandler(eventHandlerType, out destinationParameters))
37: {
38: throw new InvalidOperationException();
39: }
40:
41: if (eventHandler.GetType() == eventHandlerType)
42: {
43: return eventHandler;
44: }
45:
46: ParameterInfo[] sourceParameters;
47: if (!IsValidEventHandler(eventHandler.GetType(), out sourceParameters))
48: {
49: throw new InvalidOperationException();
50: }
51: Type[] paramTypes = new Type[destinationParameters.Length + 1];
52: paramTypes[0] = eventHandler.GetType();
53: for (int i = 0; i < destinationParameters.Length; i++)
54: {
55: paramTypes[i + 1] = destinationParameters[i].ParameterType;
56: }
57: DynamicMethod method = new DynamicMethod("WrappedEventHandler", null, paramTypes);
58: MethodInfo invoker = paramTypes[0].GetMethod("Invoke");
59: ILGenerator il = method.GetILGenerator();
60: il.Emit(OpCodes.Ldarg_0);
61: il.Emit(OpCodes.Ldarg_1);
62: il.Emit(OpCodes.Ldarg_2);
63: if (!sourceParameters[1].ParameterType.IsAssignableFrom(destinationParameters[1].ParameterType))
64: {
65: il.Emit(OpCodes.Castclass, sourceParameters[1].ParameterType);
66: }
67: il.Emit(OpCodes.Call, invoker);
68: il.Emit(OpCodes.Ret);
69: return method.CreateDelegate(eventHandlerType, eventHandler);
70: }
71:
72: public static TDelegate Convert<TDelegate>(Delegate eventHandler)
73: {
74: return (TDelegate)(object)Convert(eventHandler, typeof(TDelegate));
75: }
76: }
其實較之上麵介紹的Emit的方式,還有一種最簡單的方式,那就是按照如下的代碼所示:直接調用Delegate的靜態方法CreateDelegate:
1: public static class EventHandlerConverter
2: {
3: public static Delegate Convert(Delegate eventHandler, Type eventHandlerType)
4: {
5: Guard.ArgumentNotNull(eventHandler, "eventHandler");
6: Guard.ArgumentNotNull(eventHandlerType, "eventHandlerType");
7: return Delegate.CreateDelegate(eventHandlerType, eventHandler.Method);
8: }
9:
10: public static TDelegate Convert<TDelegate>(Delegate eventHandler)
11: {
12: return (TDelegate)(object)Convert(eventHandler, typeof(TDelegate));
13: }
14: }
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-25 16:05:11