最新文章 (全部类别)
OneDrive安装闪退解决方案
VS2022正在加载设计器,连接到已命名的管道时发生超时
客户端部署.NET7程序 | 客户端安装环境
DevExpress.XtraTreeList.TreeList 组件自动定位当前记录
塑木地板行业ERP - 用户操作手册 - 软件安装&部署
塑木地板行业ERP - 用户操作手册 - 用户管理
塑木地板行业ERP - 用户操作手册 - 角色管理
塑木地板行业ERP - 用户操作手册 - 成品出入库历史记录
塑木地板行业ERP - 用户操作手册 - 成品出库(客户发货单)
塑木地板行业ERP - 用户操作手册 - 客户送货单(成品出库)
塑木地板行业ERP - 用户操作手册 - 销售订单
塑木地板行业ERP - 用户操作手册 - 成品入库
塑木地板行业ERP - 用户操作手册 - 成品库存查询
塑木地板行业ERP - 用户操作手册 - 仓库管理
塑木地板行业ERP - 用户操作手册 - 生产单
塑木地板行业ERP - 用户操作手册 - 物料类别管理
塑木地板行业ERP - 用户操作手册 - 物料清单
塑木地板行业ERP - 用户操作手册 - 客户管理
CSFrameworkV5-客户案例 - 科伦药业-骨折联络服务管理系统
装机必备|WindowsX64|官方软件下载|.NET开发人员
Winform+DevExpress使用GridLookUpEdit实现订单明细选择商品,并自动添加新商品记录
VS2022 关停ServiceHub.IntellicodeModelService.exe服务占用CPU及内存过高
修改密码 - MiniFramework蝇量框架 - Winform框架
主窗体导航菜单NavbarControl 介绍 - MiniFramework蝇量框架 - Winform框架
.NETCore WebApi阻止接口重复调用(请求并发操作)
VS2022消除编译警告
“SymmetricAlgorithm.Create(string)”已过时:“Cryptographic factory methods accepting an algorithm name are obsolete. Use the parameterless Create factory method on the algorithm type instead
SHA256Managed/SHA512Managed已过时:Derived cryptographic types are obsolete. Use the Create method on the base type instead
MD5CryptoServiceProvider已过时:Derived cryptographic types are obsolete. Use the Create method on the base type instead
C#使用HttpClient获取IP地址位置和网络信息
判断IP是否是外网IP、内网IP
C#使用HttpClient获取公网IP
WebRequest.Create(string)已过时:WebRequest, HttpWebRequest, ServicePoint, and WebClient are obsolete. Use HttpClient instead
C#根据第三方提供的IP查询服务获取公网外网IP地址
html/dom/js/javascript开发记录
调试ASP.NETCore Web站点 - 清理IISExpress缓存数据(js,css)
EFCore+Oracle根据不同的Schema连接数据库
主程序集成CSFramework.EF 数据库框架(.NET7版本)
CSFramework.EF数据库框架简介(.NET8+EFCore)
迁移ECS服务器:导致ORACLE监听服务启动不了解决方案
SQLite数据库
VS2022编译报错:Visual Studio 容器工具需要 Docker Desktop
.NET 9 预览版+C#13新功能
EFCore禁用实体跟踪
WebApi开发框架V3.0 (.NETCore+EFCore) 增加AppSettings全局参数类
C#获取应用程序所有依赖的程序集
LINQ Expression 多条件复合条件组合(And/Or)
CSFrameworkV6客户案例 - MHR - 宁德时代制造人力资源系统
CS软件授权注册系统V3 - 发布证书
C/S软件授权注册系统V3.0(Winform+WebApi+.NET8+EFCore版本)
.net敏捷开发,创造卓越

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#.NET 删除事件所有订阅
C#获取按钮的EventClick事件,EventHandlerList委托的调用列表
解决方案:C# 当按钮不可见时(Visible=False),调用Button.PerformClick事件无效!
C# DevExpress TextEdit组件EditValueChanged事件判断触发事件来源
C#调用C++编译的DLL详解
C#拖放技术(Drop&Drag)相关方法和事件
WinFramework轻量级开发框架 - 功能按钮事件详解
C# WebService异步处理/异步调用详解
C# DataGridView 单元格按钮点击事件CellContentClick
[原创]老鼠->猫->人 事件触发
WCF开发框架之ICommunicationObject 对象详解
实例讲解基于事件的银行营销系统架构

热门标签
软件著作权登记证书 .NET .NET Reactor .NET5 .NET6 .NET7 .NET8 .NET9 .NETFramework APP AspNetCore AuthV3 Auth-软件授权注册系统 Axios B/S B/S开发框架 B/S框架 BSFramework Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFramework.License CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 CSFrameworkV6.1 CSFrameworkV6旗舰版 DAL数据访问层 Database datalock DbFramework Demo教学 Demo实例 Demo下载 DevExpress教程 Docker Desktop DOM ECS服务器 EFCore EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS MySql NavBarControl NETCore Node.JS NPM OMS Oracle资料 ORM PaaS POS Promise API PSD RedGet Redis RSA SAP Schema SEO SEO文章 SQL SQLConnector SQLite SqlServer Swagger TMS系统 Token令牌 VS2022 VSCode VS升级 VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 XtraReport 安装环境 版本区别 报表 备份还原 踩坑日记 操作手册 达梦数据库 代码生成器 迭代开发记录 功能介绍 官方软件下载 国际化 基础资料窗体 架构设计 角色权限 开发sce 开发工具 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 框架升级 毛衫行业ERP 秘钥 密钥 权限设计 软件报价 软件测试报告 软件加壳 软件简介 软件开发框架 软件开发平台 软件开发文档 软件授权 软件授权注册系统 软件体系架构 软件下载 软件著作权登记证书 软著证书 三层架构 设计模式 生成代码 实用小技巧 视频下载 收钱音箱 数据锁 数据同步 塑木地板行业ERP 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 新功能 信创 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序
联系我们
联系电话:13923396219(微信同号)
电子邮箱:23404761@qq.com
站长微信二维码
微信二维码