C#深入剖析事件(C# Event详解)


C#深入剖析事件(C# Event详解)准备写一个系列文章,深入探讨C#及.Net中的某些特性。

第一篇    事件

事件相信每个人都不陌生,随便一个WinForm程序,就会使用大量的事件,比如:
class MainForm : Form
{
   public MainForm()
   {
      this.Click += new EventHandler(MainForm_Click);
   }
   private void MainForm_Click(object sender, EventArgs e)
   {
   }
}


当然,还可以对代码进行简化,如类型的自动推断,匿名方法,Lambda表达式等。这个事件大概的工作流程为:当用户单击窗体时,操作系统向应用程序发送一系列消息,如左键按下和左键抬起,应用程序将通过GetMessage等方法最终将消息提交到窗口过程(WndProc),窗口过程通过处理消息,当发现产生了连续的左键按下和左键抬起的消息后,
就知道产生了单击事件,于是去调用窗体的OnClick方法,该方法会去检测一下是否订阅了Click事件,如果订阅了,就会去调用相应的事件处理程序,这个过程是通过委托实现的。

下面我从语法角度来分析一下事件:
事件是类、结构或接口中的一个成员,它有两种定义形式:

一、

event
MethodInvoker OneEvent;


二、


event
MethodInvoker OneEvent
{
   add
   {
   }
   remove
   {
   }
}



其中MethodInvoker是一个没有参数和返回值的委托,它只是用来约束事件处理程序的的形式,你可以任意定义一个,例如你可以使用Action来代替。
事件包括两个访问器,其中add访问器会在订阅事件时触发,remove访问器在取消事件订阅时触发。
对于第一种定义形式,系统会自动提供add及remove访问器,同时会提供如下字段:


private
MethodInvoker OneEvnet;


该字段的类型为委托的类型,字段名跟事件名相同(一个类中拥有同名成员,C#编译器是不允许的,但是系统可以)。

class
Demo
{
   public void InvokeEvent()
   {
      if (OneEvent != null)
      OneEvent();//调用事件
      if (TwoEvent != null)
      {
         string str = TwoEvent(217);//调用事件
         MessageBox.Show(str);
      }
   }
   public event MethodInvoker OneEvent;
   public event Func<int, string> TwoEvent;
}
private void button1_Click(object sender, EventArgs e)
{
   Demo de = new Demo();
   de.OneEvent += delegate
   {
      MessageBox.Show("事件被调用");
      };
      de.TwoEvent += arg => arg.ToString();
      de.InvokeEvent();
   }
   
   

可以看出,这里事件类似于方法和委托,可以传递参数并被调用。事实上,这只是编译器的一种包装,这里其实使用的正是前面提到的同名的委托字段。而如果是在一个类中访问另一个类中的事件,或者如下面将要提到的自己提供访问器的情况,由于不存在同名的委托字段,事件就不能再这样使用了,而只能出现在+=和-=运算符的左侧。

至少有两个理由使得我们需要自己提供访问器:
1. 希望在订阅或取消事件时执行一段代码。
2. 前面提到,如果不提供访问器,每定义一个事件,系统就会生成一个同名的委托字段,如果事件特别多,这就是一项巨大的开销,而如果定义了访问器,则不再提供,此时我们可以用一种统一的方式来处理,从而节省资源,实际上,WinForm就是这样处理的。

class Demo
{
   public void InvokeEvent()
   {
      if(ehl[oneEvent]!=null)
      ehl[oneEvent].DynamicInvoke();//调用事件
      if (ehl[twoEvent] != null)
      {
         string str = ehl[twoEvent].DynamicInvoke(217) as string;//调用事件
         MessageBox.Show(str);
      }
   }
   EventHandlerList ehl = new EventHandlerList();
   static readonly object oneEvent = new object();
   static readonly object twoEvent = new object();
   public event MethodInvoker OneEvent
   {
      //在add和remove访问器中,类似属性,存在一个value,表示要订阅和取消的委托
      add
      {
         //我这里的条件没有什么实际意义,只是想说明可以在访问器中执行代码
         //示例中,起到一个筛选的作用,只有那些以”On”开头,并且定义于其他类中的方法才能被订阅
         if (value.Method.Name.StartsWith("On") && value.Target != this)
         ehl.AddHandler(oneEvent,value);
      }
      remove
      {
         //对不起,禁止你取消静态方法(为什么禁止取消静态方法?没有理由,只用于举例^-^)
         if (!value.Method.IsStatic)
         ehl.RemoveHandler(oneEvent,value);
      }
   }
   public event Func<int, string> TwoEvent
   {
      add
      {
         ehl.AddHandler(twoEvent,value);
      }
      remove
      {
         ehl.RemoveHandler(twoEvent,value);
      }
   }
   public void OnCall()
   {
      MessageBox.Show("Demo.OnCall");
   }
}
class Pro
{
   public void Call()
   {
      MessageBox.Show("Pro.Call");
   }
   public void OnCall()
   {
      MessageBox.Show("Pro.OnCall");
   }
   public static void OnCalls()
   {
      MessageBox.Show("Pro.Static.OnCalls");
   }
}
private void button1_Click(object sender, EventArgs e)
{
   Pro pr = new Pro();
   Demo de = new Demo();
   de.OneEvent += pr.Call;//不以”On”开头,不会订阅
   de.OneEvent += pr.OnCall;//成功订阅
   de.OneEvent += Pro.OnCalls;//成功订阅
   de.OneEvent += de.OnCall;//只有定义在其他类中的方法才会被订阅
   de.InvokeEvent();
   de.OneEvent -= pr.Call;//未订阅,谈不上取消
   de.OneEvent -= pr.OnCall;//成功取消
   de.OneEvent -= Pro.OnCalls;//静态方法不会被取消
   de.OneEvent -= de.OnCall; //未订阅,谈不上取消
   de.InvokeEvent();
}


可以看到,只有pr.OnCall和Pro.OnCalls订阅成功了,并且Pro.OnCalls不能被取消。

再来简单说说WinForm事件:
WinForm的根是组件(Component),可视的组件称为控件(Control)。
Component上定义了一个受保护的属性Events,用于管理事件列表。
Control上定义了一系列静态的私有字段,为事件列表提供索引键,字段名基本上是:
Event事件名
如EventClick,EventEnter等。
举一个应用:
如何获取一个事件订阅的所有方法列表,以及如何在不知道事件处理程序方法名的情况下取消事件,或者更现实一点,如何取消匿名方法(在不声明一个委托引用的前提下,匿名方法显然不可能通过-=运算符来取消)。


private
void button1_Click(object sender, EventArgs e)
{
   PropertyInfo pi= typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
   EventHandlerList ehl = pi.GetValue(button2, null) as EventHandlerList;
   FieldInfo fi = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
   object key=fi.GetValue(null);
   Delegate del= ehl[key];
   foreach (Delegate de in del.GetInvocationList())
   {
      Console.WriteLine(de.Method.Name);//订阅的事件
      ehl.RemoveHandler(key, de);//取消订阅
   }
}



这段代码可以显示button2的Click事件订阅的所有方法,并且在执行该段代码后,button2的Click事件将失效。
也许有人会有这样的疑问:GetInvocationList获得的委托数组中,某个委托如果还是包括多个方法链怎么办?微软为大家想得非常周到了——数组中的每个委托都仅表示一种方法。
另外,如果我们需要取消所有事件,不用遍历,直接调用EventHandlerList.Dispose方法即可。

关于WinForm事件,还有一个有趣的现象:
当我们拖曳一个控件到窗体时,双击该控件就会进入某个事件处理程序,例如双击Button控件,会进入Click事件;双击TextBox控件,会进入TextChanged事件。你是否思考过怎么通过编程的方法知道会进入哪个事件呢?
其实这叫做默认事件(类似的,还有一个默认属性的概念),是一个特性:DefaultEventAttribute


[DefaultEvent("Click")]
class Control
{

}


Click是应用于Control类的默认事件,Button则继承了这个特性,而TextBoxBase这个类将这个特性修改为TextChanged, TextBox又从TextBoxBase继承过来这个特性。
既然知道了原理,要去检索,就很简单了,直接反射就行了。另外,其实系统提供有专门的方法:


TypeDescriptor.GetDefaultEvent。


现在来看看TreeView控件的默认事件:


Attribute attr= Attribute.GetCustomAttribute(typeof(TreeView),typeof(DefaultEventAttribute));
DefaultEventAttribute de = attr as DefaultEventAttribute;
MessageBox.Show(de.Name);


或者

MessageBox.Show(TypeDescriptor.GetDefaultEvent(typeof(TreeView)).Name);


结果正是AfterSelect。

原帖CSDN:http://topic.csdn.net/u/20090323/07/518a3d98-7d2b-4b80-ba3f-8d4e047ba8be.html

本文来源:
版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:[推荐]C#图像处理(Image Processing using C#)
下一篇:C#版智能五子棋游戏(1)
评论列表

发表评论

评论内容
昵称:
关联文章

C#深入剖析事件(C# Event详解)
C#委托(Delegate)事件(Event)应用详解 (原)
DevExpress GridView单元格CellValueChanged事件详解
C#.Net窗体多重继承构造器及Load事件执行顺序详解
ButtonStateChanged事件详解 - 当按钮状态改变时触发的事件
系统部署之B/S结构C/S结构剖析
C# 跟踪对象的所有事件触发
C#.Net反射(Reflaction)技术实例详解
C#获取按钮的EventClick事件,EventHandlerList委托的调用列表
解决方案:C# 当按钮不可见时(Visible=False),调用Button.PerformClick事件无效!
C#调用C++编译的DLL详解
C# DevExpress TextEdit组件EditValueChanged事件判断触发事件来源
C#拖放技术(Drop&Drag)相关方法和事件
WinFramework轻量级开发框架 - 功能按钮事件详解
C# WebService异步处理/异步调用详解
C# DataGridView 单元格按钮点击事件CellContentClick
[原创]老鼠->猫->人 事件触发
WCF开发框架之ICommunicationObject 对象详解
C# WebService代理类详解
实例讲解基于事件的银行营销系统架构

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序