閱讀880 返回首頁    go 阿裏雲 go 技術社區[雲棲]


“協變”、“逆變”與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

  上一篇:go  Delegate如何進行類型轉換?
  下一篇:go  基於容器的全鏈路運維平台實踐