深刻理解:C#中的委托、事件
C#中的事件还真是有点绕啊,以前用JavaScript的我,理解起来还真是废了好大劲!刚开始还真有点想不明白为什么这么绕,想想和JS的区别,最后终于恍然大悟!
C#中事件绕的根本原因:
- C#的方法,它不是一个类型,它只是其它类型的成员;
- C#是一个强类型的语言,定义方法时,它的参数必须指定类型,如
public void add(int n){...};
所以,一个方法不能直接作为其它方法的参数,把一个方法名作为参数,无法指定类型啊,会报错!那我就想啊,既然不能直接传入,那我传入整个对象总可以吧,通过传进来的对象来执行该方法,如下代码:
using System;
namespace MyEventTest
{
public class SomeClass
{
public void Start(int a) { Console.WriteLine("Go:{0}",a); }
}
public class Publisher
{
public void StartEvent(int a, SomeClass sc)
{
if (sc != null)
{
sc.Start(a); //触发回调方法
}
}
}
public class MainClass
{
static void Main()
{
SomeClass some = new SomeClass();
Publisher p = new Publisher();
p.StartEvent(5,some); //Go:5
}
}
}
以上方法确实可以,但C#不完全是这样实现事件的,因为方法的特殊性,C#引入了委托的概念,让委托对象来代表方法作为其它方法的参数;而事件对象,其实就是一个委托对象。下面先介绍一下委托:
委托
对应于以上方法:
public void Start(int a) { Console.WriteLine("Go:{0}",a); }
我们可以定义一个委托类型:
public delegate void MyDel(int a);
委托的定义:
- 委托MyDel它是一个类型,类型名就是MyDel;定义委托相当于定义一个新类,委托在后台实现为派生自System.Delegate类。
- 定义委托,就是告诉编译器该委托将表示哪种方法(返回值类型+方法签名),该方法可以是任意类型的实例方法、静态方法,只要方法的签名、返回值类型与委托匹配,那么该委托的实例就可以引用这些方法。
- 使用委托,必须创建该委托的实例,并为它指定要引用的方法,如:
MyDel d = some.Start;注意这里不是some.Start(); - 委托对象支持"+","+="来为它添加更多的方法引用,而"-","-="则是删除引用;
- 引用了多个方法的委托就叫多播委托,多播委托派生自基类
System.MulticastDelegate类,它是System.Delegate类的子类 - 注意:只要委托对象还存在对方法的引用,它就一直占用内存哦!我想可以用
d = null;来释放委托对象d; - 可以对委托对象执行调用,如:
d(5);它将把调用传递给它所引用的方法some.Start(5);,对于多播委托,它将按顺序调用它引用的所有方法,但如果其中一个方法抛出异常,且没在方法内部处理,则将会将异常往外抛出,之后的方法调用将终止。
使用委托的规则:
- 委托是和类一个级别的,可以在能定义类的任何地方定义委托;
- 委托不支持继承;
- 可以为委托类型定义任意常见的访问修饰符;
- 委托对象所引用的方法也可以是匿名方法、Lambda 表达式;
- 多播委托的返回值类型必须是void,否则就只能得到委托调用的最后一个方法的结果。
- 在.NET 4.0中,委托开始支持协变与逆变,这样一来,定义委托类型时的签名可以和所要引用的方法的签名不完全匹配(不同类型之间必须是派生关系)
- 委托支持泛型,.NET预定义了两个泛型版本的委托:
Action< T >委托表示引用一个返回值类型为void的方法,根据参数个数存在不同的变体版本;如:Action<in T1, in T2>Func< T >委托表示引用一个带返回值类型的方法,根据参数个数存在不同的变体版本;如:Func<in T1, out TResult>1个参数T1和返回值类型TResult。
事件
说完了委托的概念,就可以继续讲事件了,因为事件是基于委托的!
事件的概念:
- 类或对象可以通过事件向其他类或对象通知发生的相关事情。
- 发送事件的类称为“发行者”,接收事件的类称为“订阅者”。(就是设计模式中的订阅发布者模式);
- 一个事件可以有多个订阅者。 一个订阅者可处理来自多个发行者的多个事件。如果一个事件有多个订阅者,当引发该事件时,会同步调用多个事件处理程序。也可异步调用。
.NET Framework 类库中的所有事件均基于 EventHandler 委托,还有泛型版本EventHandler<EventArgs>,这个委托是.NET预定义的,不需要我们定义,可以直接用它来实例化一个事件对象,定义如下:
参数object sender对象是对发布者的实例的引用,EventArgs e对象主要用来存储事件数据
public delegate void EventHandler(object sender, EventArgs e); //EventArgs主要用来存储事件数据
public delegate void EventHandler<TEventArgs>(object sender, EventArgs e);
虽然在自定义的类中的事件可基于任何有效委托类型,但是,通常建议使用.NET预定义事件委托类型让事件基于 .NET 标准事件模式
下面是我总结的发布基于 .NET 标准事件模式的4个步骤:
第1步:在发布者类中实例化委托事件,并定义一个实例方法,用来调用委托事件(因为委托事件只能通过定义它的类的实例来调用)。
定义发布者类之前可先定义一个用来存储事件数据的类(它必须派生于EventArgs基类),如下:
注意:在方法StartEvent()中,声明了一个变量,来保存事件对象的副本,这样在取得事件对象的副本后,到触发事件时,这段时间内,这个事件副本就不会受其它线程的影响。如:在此期间,其它线程注销了回调方法,那么MyEvent就为null了,这时再触发事件将引发错误。(这就是线程安全的事件,当然还可以通过锁机制,或者为事件对象始终引用一个空方法)
public class MyEventArgs: EventArgs //定义存储事件数据的类
{
public int Current{get;set;}
}
public class Publisher
{
public event EventHandler<MyEventArgs> MyEvent; //第1步:实例化委托事件
public int Sum{get;set;}
public void StartEvent(int a)
{
var EventCopy = MyEvent; //每次都取一个副本
MyEventArgs args = new MyEventArgs();
args.Current = a;
this.Sum += a;
if (EventCopy != null)
{
EventCopy(this,args); //调用事件
}
}
}
第2步:定义订阅者类,在该类中定义和委托事件相匹配的方法(事件触发时,实际要执行的方法)
public class Subscriber
{
public void Dosomething1(object obj, MyEventArgs e)
{
Publisher p = (Publisher)obj;
Console.WriteLine("Meg: Sum = {0}, Current = {1}", p.sum, e.Current);
}
public void Dosomething2(object obj, MyEventArgs e)
{
}
}
第3步:在客户端代码中,在发布者类的实例上为委托事件注册回调方法
public class MainClass
{
static void Main()
{
Publisher p = new Publisher{ Sum = 0 };
Subscriber sub = new Subscriber();
p.MyEvent += sub. Dosomething1; //注册回调方法
p.MyEvent += sub. Dosomething2;
p. StartEvent( 5 ); //调用方法,间接触发事件
p.MyEvent -= sub. Dosomething1; //取消注册
}
}
要点:事件对象其实就是一个委托对象,把事件当委托来看,就比较容易理解了!不要被Event这个单词给蒙蔽了!
介绍完了!下回将介绍C#中的其它一些较难理解的概念!
深刻理解:C#中的委托、事件的更多相关文章
- 怎么理解js中的事件委托
怎么理解js中的事件委托 时间 2015-01-15 00:59:59 SegmentFault 原文 http://segmentfault.com/blog/sunchengli/119000 ...
- 深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因
声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/det ...
- [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式
使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...
- 学习和理解C#中的事件
注:本文系学习笔记. 上一篇文章记录了我对C#中委托的理解.委托实际上是一种类型.可以将一个或多个方法绑定到委托上面,调用委托时,一次执行委托上面绑定的方法.本文要讲述的事件实际上和委托有很深的“感情 ...
- 【Swing】理解Swing中的事件与线程
talk is cheap , show me the code. Swing中的事件 事件驱动 所有的GUI程序都是事件驱动的.Swing当然也是. GUI程序不同于Command Line程序,一 ...
- 理解JavaScript中的事件轮询
原文:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 为什么JavaScript是单线程 JavaScript语言的一大特点就是单线程,也 ...
- 理解Javascript中的事件绑定与事件委托
最近在深入实践js中,遇到了一些问题,比如我需要为动态创建的DOM元素绑定事件,那么普通的事件绑定就不行了,于是通过上网查资料了解到事件委托,因此想总结一下js中的事件绑定与事件委托. 事件绑定 ...
- 一个demo让你彻底理解Android中触摸事件的分发
注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOW ...
- 深刻理解Java中的String、StringBuffer和StringBuilder的差别
声明:本博客为原创博客,未经同意.不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(链接为http://blog.csdn.net/bettarwang/article/detai ...
- 再次理解javascript中的事件
一.事件流的概念 + 事件流描述的是从页面中接收事件的顺序. 二.事件捕获和事件冒泡 + 事件冒泡接收事件的顺序:
随机推荐
- docker4dotnet #2 容器化主机
.NET 猿自从认识了小鲸鱼,感觉功力大增.上篇<docker4dotnet #1 前世今生&世界你好>中给大家介绍了如何在Windows上面配置Docker for Window ...
- SQL Server常见问题介绍及快速解决建议
前言 本文旨在帮助SQL Server数据库的使用人员了解常见的问题,及快速解决这些问题.这些问题是数据库的常规管理问题,对于很多对数据库没有深入了解的朋友提供一个大概的常见问题框架. 下面一些问题是 ...
- BZOJ 1597: [Usaco2008 Mar]土地购买 [斜率优化DP]
1597: [Usaco2008 Mar]土地购买 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 4026 Solved: 1473[Submit] ...
- 从淘宝 UWP 的新功能 -- 比较页面来谈谈 UWP 的窗口多开功能
前言 之前在 剁手党也有春天 -- 淘宝 UWP ”比较“功能诞生记 这篇随笔中介绍了一下 UWP 淘宝的“比较”新功能呱呱坠地的过程.在鲜活的文字背后,其实都是程序员不眠不休的血泪史(有血有泪有史) ...
- Lesson 19 Sold out
Text 'The play may begin at any moment,' I said. 'It may have begun already,' Susan answered. I hurr ...
- ReactNative&weex&DeviceOne对比
React Native出来有一段时间了,国内的weex和deviceone是近期发布的,我可以说从2011年就开始关注快速开发的跨平台平台技术了,接触过phoneGap.数字天堂.appcan等早期 ...
- 惊心动魄的一上午,感谢eclipse 的文件恢复功能
昨晚倒腾了半天android 的程序,夜里三点多了,不争气的笔记本由于太热,突然熄火.话说就在昨天还在想着一定要把东西放到svn上,防止文档找不到或者笔记本丢失带来的严重后果.呵呵,就是这么想着,今天 ...
- Linex 配置php服务器
此文是可以参考 楼主也不是系统管理员只是迫不得已所以自己才找的 大家可以参考 .... ..... 安装apache 安装mysql 安装PHP 测试服务器 php -v 查询php的版本 就这些了 ...
- Atitit rss没落以及替代品在线阅读器
Atitit rss没落以及替代品在线阅读器 1.1. 对RSS的疯狂追逐,在2005年达到了一个高峰.1 1.2. Rss的问题,支持支rss,不支持url1 1.3. ,博客受到社交网络的冲击.s ...
- Atitit 动态按钮图片背景颜色与文字组合解决方案
Atitit 动态按钮图片背景颜色与文字组合解决方案 转换背景颜色,setFont("cywe_img", fontScale, 50, 5) 设置文字大小与坐标 文字分拆,使用字 ...