“协变”、“逆变”与Delegate类型转换

我在发表了《Delegate如何进行类型转换?》之后又想到了其他一些相关的东西,除了简单地分析如何通过Emit实现EventHandler的类型转换之外,还加上关于Delegate“协变”与“逆变”的一些东西,算是对前一篇文章的完善。

目录 一、从Delegate的“协变”与“逆变”说起 二、EventHandler<TEventArgs>是否换一种定义方式更好? 三、“统一的事件注册”能否应用于一般形式? 四、通过Emit实现EventHandler的类型转换 五、最简单的转换方式

一、从Delegate的“协变”与“逆变”说起

根据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 { }

二、EventHandler<TEventArgs>是否换一种定义方式更好?

事件(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<in 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<EventArgs> 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 EventHandler<BarEventArgs> Bar;
  11:     public event EventHandler<BazEventArgs> Baz;
  12:     public event EventHandler<QuxEventArgs> 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, EventHandlerConverter.Convert(eventHandler, eventInfo.EventHandlerType));
   8:         }
   9:     }
  10: }

那么现在我们将Foo类型的三个事件类型定义成普通的Delegage:BarEventHandler、BazEventHandler和QuxEventHandler,实际上我们上面的代码依然可以执行。

   1: public delegate void BarEventHandler(object sender, BarEventArgs e);
   2: public delegate void BazEventHandler(object sender, BazEventArgs e);
   3: public delegate void QuxEventHandler(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实现EventHandler的类型转换

我们通过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: }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的SOD蜜

不使用反射的实体类方案

看过很多ORM的实体类方案,大多是用反射来读数据库数据,这样当频繁操作实体类的时候效率很低,我借鉴了一位朋友的思路,采用.NET 2.0的泛型技术,为实体类提供...

22380
来自专栏C++

Windows核心编程:第2章 字符和字符串处理

13740
来自专栏GreenLeaves

EF基础知识小记六(使用Code First建模自引用关系,常用于系统菜单、文件目录等有层级之分的实体)

日常开发中,经常会碰到一些自引用的实体,比如系统菜单、目录实体,这类实体往往自己引用自己,所以我们必须学会使用Code First来建立这一类的模型. 以下是自...

22760
来自专栏小樱的经验随笔

Codeforces 706B Interesting drink

B. Interesting drink time limit per test:2 seconds memory limit per test:256 meg...

30480
来自专栏自由而无用的灵魂的碎碎念

通过写Java代码来对MyEclipse进行注册

import java.io.BufferedReader; import java.io.IOException; import java.io...

13540
来自专栏草根专栏

使用xUnit为.net core程序进行单元测试(中)

第一部分: https://cloud.tencent.com/developer/article/1019835

27380
来自专栏技术栈大杂烩

Python: 函数与方法的区别 以及 Bound Method 和 Unbound Method

随着我们越来越频繁使用Python, 我们难免会接触到类, 接触到类属性和方法.但是很多新手包括我, 不知道方法 和 函数 的区别,这次简单来讨论下, 如果有哪...

34410
来自专栏草根专栏

使用xUnit为.net core程序进行单元测试(2)

下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] public void ...

54970
来自专栏大内老A

Delegate如何进行类型转换?

我们知道对于两个不具有继承关系的两个类型,如果没有为它们定义转换器,两这之间的类型转换是不允许的,Delegate也是如此。但是有时候我们却希望“兼容”的两种D...

21280
来自专栏Golang语言社区

Golang中time包用法--转

time包中包括两类时间:时间点(某一时刻)和时常(某一段时间) 1时间常量(时间格式化) const ( ANSIC = "Mon Jan...

1.6K80

扫码关注云+社区

领取腾讯云代金券