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


Delegate如何進行類型轉換?

我們知道對於兩個不具有繼承關係的兩個類型,如果沒有為它們定義轉換器,兩這之間的類型轉換是不允許的,Delegate也是如此。但是有時候我們卻希望“兼容”的兩種Delegate類型能夠進行轉換,比較典型的就是表示事件的Delegate。.NET Framework為我們定義了類型EventHandler來表示事件,但是卻沒有規定事件的Delegate類型是EventHandler的子類。原則上講,事件可以是任意類型的Delegate,但是我們使用的事件一般具有如下兩個共同點:

  • 不具有返回類型,或者返回類型為void;
  • 有且隻有兩個輸入參數,其一個參數類型為Object,第二個類型是EventArgs的子類。

如果事件的類型不是EventHandler的子類,我們是不可以將一個EventHandler對象對事件進行注冊的。如果我們能夠將EventHandler對象轉換成事件對應的類型,那麼就可以到達這樣的目的:將同一個EventHandler注冊給任意的事件。我們舉個簡單的例子,假設我們具有這樣一個需求:對於指定的某個對象,需要在它每一個事件觸發的時候我們進行響應的日誌記錄。具體實現如下麵的代碼所示,具體的日誌記錄實現在Log方法中,RegisterEventHandler<T>方法中我們通過反射的方式獲取類型T中定義的所有Event,並將指定的EventHandler針對這些事件進行注冊。由於類型可能不一致,我們通過調用自定義的EventHandlerConverter的靜態方法Convert進行類型轉換。[源代碼從這裏下載]

   1: static void RegisterEventHandler<T>(T target, EventHandler eventHandler)
   2: {
   3:     EventInfo[] events = typeof(T).GetEvents();
   4:     foreach (EventInfo eventInfo in events)
   5:     {
   6:         eventInfo.AddEventHandler(target, );
   7:     }
   8: }

我們通過如下的代碼定義了一個類型Foo,它具有Bar、Baz和Qux三個事件,其Delegate類分別是BarEventHandler、BazEventHandler和QuxEventHandler。當RaiseEvents方法被調用的時候,注冊的三個事件被觸發。

   1: public class BarEventArgs : EventArgs
   2: { }
   3: public class BazEventArgs : EventArgs
   4: { }
   5: public class QuxEventArgs : EventArgs
   6: { }
   7:  
   8: public delegate void BarEventHandler(object sender, BarEventArgs e);
   9: public delegate void BazEventHandler(object sender, BazEventArgs e);
  10: public delegate void QuxEventHandler(object sender, QuxEventArgs e);
  11:  
  12: public class Foo
  13: {
  14:     public event BarEventHandler Bar;
  15:     public event BazEventHandler Baz;
  16:     public event QuxEventHandler Qux;
  17:        
  18:     public void RaiseEvents()
  19:     {
  20:         if (null != Bar) Bar(this, new BarEventArgs());
  21:         if (null != Baz) Baz(this, new BazEventArgs());
  22:         if (null != Qux) Qux(this, new QuxEventArgs());
  23:     }
  24: }

現在我們在Main方法中編寫如下的程序。從輸出結果可以看出,同一個EventHandler是否能夠成功注冊給Foo中不同類型的三個事件。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Foo foo = new Foo();
   6:         RegisterEventHandler<Foo>(foo, Log);
   7:         foo.RaiseEvents();
   8:     }
   9:  
  10:     static void Log(object sender, EventArgs e)
  11:     {
  12:         Console.WriteLine("{0}: {1}", sender.GetType().Name, e.GetType().Name);
  13:     }        
  14: }

輸出結果:

   1: Foo: BarEventArgs
   2: Foo: BazEventArgs
   3: Foo: QuxEventArgs

實現在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: }

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 16:05:16

  上一篇:go  無需寫try/catch,也能正常處理異常
  下一篇:go  “協變”、“逆變”與Delegate類型轉換