Delegate = Object + MethodInfo

其实你完全可以通过Reflector这样的工具来看Delegate类型是如何定义的。在这里,我们只关注Delegate本质的东西,即Delegate最终是如果执行的。为此,我创建了下面一个简单的MyDelegate类型来模拟Delegate

复制代码
 1: public class MyDelegate
复制代码
 2: { 
复制代码
 3:  
复制代码
 4: public object Target { get; private set; }
复制代码
 5: public MethodInfo Method { get; private set; } 
复制代码
 6:  
复制代码
 7: public MyDelegate(object target, MethodInfo method)
复制代码
 8: {
复制代码
 9: this.Target = target;
复制代码
 10: this.Method = method;
复制代码
 11: } 
复制代码
 12:  
复制代码
 13: public virtual void Invoke(params object[] args)
复制代码
 14: {
复制代码
 15: this.Method.Invoke(this.Target, args); 
复制代码
 16: } 
复制代码
 17: }

从上面的定义可以看到,MyDelegate只有两个属性:Object类型的Target和MethodInfo类型的Method。委托的执行通过需方法Invoke完成,具体来说,最终的执行通过反射的方式调用Method的Invoke方法完成。

二、MulticastDelegate对象多个Delegate对象的链表

其实我们平时讲的委托,并不是一个单个的Delegate对象,实际上是一个委托链,这样一个委托链通过MulticastDelegate定义。由于定义也相对复杂,我们同样通过定义模拟类型来反映其本质的东西。为此,我创建了如下一个MyMulticastDelegate类型。

复制代码
 1: public class MyMulticastDelegate : MyDelegate
复制代码
 2: {
复制代码
 3: public MyMulticastDelegate Next { get; set; }
复制代码
 4:  
复制代码
 5: public MyMulticastDelegate(object target, MethodInfo method)
复制代码
 6: : base(target, method)
复制代码
 7: { }
复制代码
 8:  
复制代码
 9: public override void Invoke(params object[] args)
复制代码
 10: {
复制代码
 11: base.Invoke(args);
复制代码
 12: if (null != Next)
复制代码
 13: {
复制代码
 14: this.Next.Invoke(args);
复制代码
 15: }
复制代码
 16: }
复制代码
 17: }

MyMulticastDelegate继承自上面定义的MyDelegate类型,在此基础上定义了一个额外的属性Next,代表委托链中当前委托对象的下一个委托。最后,Invoke方法被重写:按照委托链的顺序依次执行每一个委托对象。

三、事件本质上是一个 MulticastDelegate 对象

我们使用的事件一般通过EventHandler或者System.EventHandler<TEventArgs>表示,其本质来时一个通过MulticastDelegate对象表示的委托链。事件注册本质就是将另外一个委托(链)连到当前委托链上。下面定义的类型MyEventHandler模拟了事件的实现。

复制代码
 1: public class MyEventHandler : MyMulticastDelegate
复制代码
 2: {
复制代码
 3: public MyEventHandler(object target, MethodInfo method)
复制代码
 4: : base(target, method)
复制代码
 5: { } 
复制代码
 6:  
复制代码
 7: public void Fire(object sender,EventArgs args)
复制代码
 8: {
复制代码
 9: this.Invoke(sender,args);
复制代码
 10: }
复制代码
 11:  
复制代码
 12: public static MyEventHandler operator +(MyEventHandler current, MyEventHandler next)
复制代码
 13: {
复制代码
 14: if (null == current)
复制代码
 15: {
复制代码
 16: return next;
复制代码
 17: }
复制代码
 18:  
复制代码
 19: MyMulticastDelegate terminator = current;
复制代码
 20: while (null != terminator.Next)
复制代码
 21: {
复制代码
 22: terminator = terminator.Next;
复制代码
 23: }
复制代码
 24:  
复制代码
 25: terminator.Next = next;
复制代码
 26: return current;
复制代码
 27: }
复制代码
 28:  
复制代码
 29: public static implicit operator MyEventHandler(EventHandler eventHandler)
复制代码
 30: {
复制代码
 31: return new MyEventHandler(eventHandler.Target, eventHandler.Method);
复制代码
 32: }
复制代码
 33: }

事件一般通过+=操作符进行注册,其本质就是将两个委托链相连。为此,在MyEventHandler中,我也重载了操作符+。事件的触发被定义在Fire方法中,其实现就是调用MyMulticastDelegate的Invoke方法。此外,我还定义一个隐式类型转换操作符,将EventHandler转对象化成MyEventHandler类型。

四、事件的订阅

现在我通过一个具体的例子来说明通过上面定义的MyEventHandler来模拟具体的事件注册和触发。在这里,我们模拟的是Button的Click事件,为此我采用标准的事件编程方式定义了如下一个Button类型。MyEventHandler类型的Click属性代表事件本身,Click操作的触发通过执行PerformClick方法完成。进一步地,Click操作的处理实现在虚方法OnClick中,其本质就是调用MyEventHandler的Fire方法。

复制代码
 1: public class Button
复制代码
 2: {
复制代码
 3: public string Id { get; private set; }
复制代码
 4: public MyEventHandler Click { get; set; }
复制代码
 5:  
复制代码
 6: public Button(string id)
复制代码
 7: {
复制代码
 8: this.Id = id;
复制代码
 9: }
复制代码
 10:  
复制代码
 11: protected virtual void OnClick(EventArgs args)
复制代码
 12: {
复制代码
 13: if(null != this.Click)
复制代码
 14: {
复制代码
 15: this.Click.Fire(this, args);
复制代码
 16: }
复制代码
 17: }
复制代码
 18:  
复制代码
 19: public void PerformClick()
复制代码
 20: {
复制代码
 21: this.OnClick(EventArgs.Empty);
复制代码
 22: }
复制代码
 23: } 

接下来,我们创建另一个模拟订阅Button对象的Click事件的类型,这样一个简单的类型Foo定义如下。当订阅的Click事件触发之后,会回调DoSomethingOnceClick方法,方法会在控制台上输出一段文字。

复制代码
 1: public class Foo
复制代码
 2: {
复制代码
 3: public void DoSomethingOnceClick(object sender, EventArgs args)
复制代码
 4: { 
复制代码
 5: Button btn = sender as Button;
复制代码
 6: if(null != btn)
复制代码
 7: {
复制代码
 8: Console.WriteLine("Click {0}", btn.Id);
复制代码
 9: }
复制代码
 10: }
复制代码
 11: }

那么最终的事件订阅和触发编写在下面代码中:在创建的Button对象中,进行了6次相同的事件注册,最终通过PerformClick方法触发事件。由于在MyEventHandler定义一个从EventHandler到MyEventHandler类型的隐式转换操作符,所以我们进行事件注册和传统的方式别无二致。

复制代码
 1: class Program
复制代码
 2: {
复制代码
 3: static void Main(string[] args)
复制代码
 4: {
复制代码
 5: var btn1 = new Button("Button1");
复制代码
 6: var foo = new Foo();
复制代码
 7: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 8: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 9: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 10: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 11: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 12: btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
复制代码
 13: btn1.PerformClick();
相关推荐
2603_9651481111 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
ltl11 小时前
RocksDB 经典故障排查:L0、compaction 与 write stall
数据库
波波00712 小时前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
xfhuangfu12 小时前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT13 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData13 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean13 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交
J_bean13 小时前
MySQL InnoDB 如何检测死锁、判定死锁、处理死锁
数据库·mysql·死锁·死锁检测·处理死锁·死锁判定
知无不研14 小时前
lambda表达式的使用(3)
开发语言·c++·lambda
浪子明X14 小时前
从 MongoDB 文档到关系模型:构建可重跑、可对账的数据迁移流水线
数据库·mongodb·oracle