Delegate & Event
Long time without coding,貌似对programming都失去了曾有的一点点sense了,今日有空再细瞄一下.net的委托和事件。
Delegate
- 首先,委托用于引用一类具有相同返回值和参数列表的方法(可以引用静态方法或者是实例方法),类似于函数指针,用于实现函数回调。
例如,我们如下声明了委托ProgressChangedDelegate,用于引用参数是int,返回void的方法。
/// <summary> /// 进度改变通知委托 /// </summary> /// <param name="value">当前进度(0~100)</param> public delegate void ProgressChangedDelegate(int value);
- 委托是类型安全的,且委托就是一个类型,它是对特定签名(返回值和参数列表)的方法的包装。
看一下上面代码生成的IL代码,编译器其实在后面帮我们生成了一个ProgressChangedDelegate的类型,
且它是继承自类型System.MulticastDelegate(多播委托)
- 委托类型的实例化。下面分别用静态方法和实例方法来实例化委托对象。
//declare and initial a delegate with static method ProgressConsoleDisplayer progressConsoleDisplayer = new ProgressConsoleDisplayer(); ProgressChangedDelegate progressDelegate = new ProgressChangedDelegate(ProgressConsoleDisplayer.DisplayProgress); //delegate refer to instance method ProgressFileDisplayer fileDisplayer = new ProgressFileDisplayer(@"c:\progress.log"); ProgressChangedDelegate anotherProgressDelegate = new ProgressChangedDelegate(fileDisplayer.DisplayProgress);
分析IL,C#简洁语法的背后,委托的实例化是通过传入方法指针和方法执行的对象的引用(静态方法是null)来执行构造函数来完成的。
- 委托的调用。委托实例调用的方式跟调用它引用的方法一样。
progressDelegate(progressValue); anotherProgressDelegate(progressValue);分析IL,C#自然的调用语法后面其实是调用委托对象的invoke方法(最后是调用所引用的MethodInfo的Invoke)。
- 既然delegate关键字声明的委托是多播委托,我们可以使用“+=”,“-=”操作符对两个委托对象进行捆绑和解除捆绑。
通过IL可以看到,这两个操作符的背后其实是调用Delegate类型的Combine和Remove两个静态方法来实现的。
- 首先,委托用于引用一类具有相同返回值和参数列表的方法(可以引用静态方法或者是实例方法),类似于函数指针,用于实现函数回调。
Delegate Chain.
上面说的delegate声明的委托多播委托,也就是说它可以绑定多个方法进行回调。如下代码,进度的变更会依次显示到控制台和记录到文本。
///
/// 进度改变通知委托 /// /// 当前进度(0~100) public delegate void ProgressChangedDelegate(int value); class Program { static void Main(string[] args) { //declare and initial a delegate with static method ProgressConsoleDisplayer progressConsoleDisplayer = new ProgressConsoleDisplayer(); ProgressChangedDelegate progressDelegate = new ProgressChangedDelegate(ProgressConsoleDisplayer.DisplayProgress); //delegate refer to instance method ProgressFileDisplayer fileDisplayer = new ProgressFileDisplayer(@"c:\progress.log"); progressDelegate += fileDisplayer.DisplayProgress; for (int progressValue = 0; progressValue /// 进度控制台输出器 /// public class ProgressConsoleDisplayer { public static void DisplayProgress(int value) { Console.WriteLine(string.Format("Console Display:Current Progress is {0}", value)); } } ////// 进度文件输出器 /// public class ProgressFileDisplayer:IDisposable { private readonly FileStream fileStream=null; private readonly StreamWriter writer = null; public ProgressFileDisplayer(string filePath) { FileStream fileStream=File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); writer = new StreamWriter(fileStream); } public void DisplayProgress(int value) { writer.WriteLine(string.Format("File Display:Current Progress is {0}", value)); writer.Flush(); } public void Dispose() { writer.Dispose(); fileStream.Dispose(); } }1.委托链是通过Delegate.Combine和Remove两个静态方法来进行绑定和删除操作的,每次Combine和Remove以后委托都会指向一个新的对象。
2.委托的调用。多个委托对象combine后会放到内部的集合(数组)中保存,当调用时会按顺序调用数组中的每个元素,调用返回的结果是最后一个委托调用返回的结果。
delegate int ReturnIntDelegate(); public static void Run() { ReturnIntDelegate delegateInstance = () => { return 1; }; delegateInstance += () => { return 2; }; delegateInstance += () => { return 3; }; int result = delegateInstance(); Console.WriteLine(string.Format("delegate call result:{0}", result)); }
委托链中委托的调用时简单的顺序调用,如果委托数组中的一个委托调用发生未处理的异常则后续的委托都不能调用,为避免这个问题,当然希望在每个委托调用时加入异常处理。又或者,我们希望每个委托在单独的线程上同时执行以避免阻塞,提高性能。总之,基于稳定和性能考虑,我们当然希望可以手工控制每个委托的调用。
使用MulticastDelegate类的GetInvocationList实例方法可以获取Delegate[]对象,通过遍历数组,控制每个Delegate委托的调用。下面修改上面的代码,加入异常处理。delegate int ReturnIntDelegate(); public static void Run() { ReturnIntDelegate delegateInstance = () => { return 1; }; delegateInstance += () => { throw new Exception(); return 2; }; delegateInstance += () => { return 3; }; //int result = delegateInstance(); int result = 0; Delegate[] delegates= delegateInstance.GetInvocationList(); if (delegates != null) { foreach (ReturnIntDelegate del in delegates) { try { result=del(); } catch (Exception ex) { //just ignore } } } Console.WriteLine(string.Format("delegate call result:{0}", result)); }
Event
事件机制是观察者模式的实现,当对象(Project)的状态改变,一个或者多个观察者(observer)会得到通知并做相应的更新。
在观察者模式中,观察者继承的基类或者接口都包含了一个方法供被观察对象来调用发送消息,这个方法其实
就是消息的契约,在事件机制中对应的就是委托,所以我们声明事件成员时需要指定事件的委托类型。
例如,我们在一个作业类Job中声明一个任务失败MissionFailed事件。
public class Task { /// <summary> /// occur when task runs failed /// </summary> public event EventHandler MissionFailed; }
这里我使用的是.net库中的委托EventHandler,为遵守建议的命名规范,即使是自定义事件的委托,名称应该以事件名称开始,以EventHandler为后缀。
被观察者应该仅发送消息,而不会关心观察者的处理消息细节,所以这个标识消息的委托应该是无返回值的。
可以看一下EventHandler及其泛型版本的定义:事件消息的内容体现在委托的方法参数中,一般包含消息的发送者对象和额外参数对象,包含额外参数的类
按照规范应该以继承自EventArgs类,且名称以EventArgs结尾。
如我们定义任务失败事件的额外信息类,包含了错误信息字段。
public class MissionFailedEventArgs:EventArgs { public MissionFailedEventArgs(string error) { Message = error; } /// <summary> /// Error Info /// </summary> public string Message { get; private set; } }相应修改事件声明:
public event EventHandler<MissionFailedEventArgs> MissionFailed;
观察者模式中被观察者内部维护了一个观察者的列表,当观察者订阅通知时会被添加到此列表。当被观察者发送消息时,它会遍历列表,执行方法发送消息。
在事件中,这个是通过委托的多播来实现的。
首先,事件成员的本质一组对应于订阅事件和取消订阅事件的方法组。(我们可以对这两个方法进行自定义来实现事件的定制)
查看IL代码,事件的默认实现中编译器会自动为我们生成一个可访问性为private的跟事件同名的委托成员,还有add_[EventName]和remove_[EventName]
两个方法,而事件的订阅+=和取消订阅-=就是调用这两个方法来实现,而这两个方法的具体实现最终还是落到Delegate的Combine和Remove操作。
所以说.net的事件是基于委托的,是委托的封装。触发事件。一般会在类中定义一个名为On[EventName]的Protected虚函数来引发事件。
/// <summary> /// Fired the MissionFailed Event /// </summary> protected virtual void OnMissionFailed(MissionFailedEventArgs e) { if (MissionFailed != null) //this should not be thread-safe { MissionFailed(this, e); } }
然后在需要的地方调用上面的方法触发事件,发送通知,如下在任务执行发生异常时触发事件。
/// <summary> /// Run Task /// </summary> public void Run() { try { } catch (Exception ex) { OnMissionFailed(new MissionFailedEventArgs(e.Message)); throw; } }
可以猜测事件触发,根本上应该是委托的调用,查看IL代码可以证实。
- 事件的自定义实现。事件成员本质是一组方法,编译器会自动生成默认的实现代码,但我们也可以自定义Add,Remove方法显示定义事件。
可以参考window Form中的事件定义,由于事件默认的隐式定义会自动为每个事件成员定义一个委托成员,而如果事件没有被使用,会造成
内存空间的浪费。所以Form中的事件实现是通过字典的形式来操作委托:
public class BusinessComponent:Component { private static object EVENT_BUSINESSSTART = new object(); private static object EVENT_BUSINESSEND = new object(); /// <summary> /// 业务开始事件 /// </summary> public event EventHandler BusinessStart { add { Events.AddHandler(EVENT_BUSINESSSTART, value); } remove { Events.RemoveHandler(EVENT_BUSINESSSTART, value); } } /// <summary> /// 业务终止事件 /// </summary> public event EventHandler BusinessEnd { add { Events.AddHandler(EVENT_BUSINESSEND, value); } remove { Events.RemoveHandler(EVENT_BUSINESSEND, value); } } }
下面尝试过滤某些事件订阅,同一个事件处理方法仅允许订阅一次。通过遍历委托调用列表,检查是否已经存在相同的委托。
private EventHandler<MissionFailedEventArgs> missionFailed; /// <summary> /// occur when task runs failed /// </summary> public event EventHandler<MissionFailedEventArgs> MissionFailed { add { Delegate[] delegates = null; if (missionFailed != null && (delegates = missionFailed.GetInvocationList()) != null) { foreach (Delegate d in delegates) { if (d.Equals(value)) return; } } missionFailed+= value; } remove { } }
调用代码对事件使用同一方法订阅了两次。
private static void TaskMissionFailedHandler(object sender, MissionFailedEventArgs e) { Console.WriteLine(string.Format("Time:{0},task run.", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))); }
static void Main(string[] args) { Task task = new Task(); task.MissionFailed += TaskMissionFailedHandler; task.MissionFailed += TaskMissionFailedHandler; task.Run(); Console.Read(); }
但只有一次订阅成功。
References:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/
http://csharpindepth.com/Articles/chapter2/events.aspx
http://www.oodesign.com/observer-pattern.html
Delegate & Event的更多相关文章
- C# delegate event func action 匿名方法 lambda表达式
delegate event action func 匿名方法 lambda表达式 delegate类似c++的函数指针,但是是类型安全的,可以指向多个函数, public delegate void ...
- [UE4] C++实现Delegate Event实例(例子、example、sample)
转自:http://aigo.iteye.com/blog/2301010 虽然官方doc上说Event的Binding方式跟Multi-Cast用法完全一样,Multi-Cast论坛上也有很多例子, ...
- [C#] Delegate, Multicase delegate, Event
声明:这篇博客翻译自:https://www.codeproject.com/Articles/1061085/Delegates-Multicast-delegates-and-Events-in- ...
- C# delegate & event
public delegate void MyDelegate(string mydelegate);//声明一个delegate对象 //实现有相同参数和返回值的函数 public v ...
- Delegate&Event
Delegate 1.基本类: public class Student { public int Id { get; set; } public string Name { get; set; } ...
- Delegate event 委托事件---两个From窗体使用委托事件
窗体如下: public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void b ...
- 委托与事件--delegate&&event
委托 访问修饰符 delegate 返回值 委托名(参数); public delegate void NoReturnNoPara(); public void NoReturnNoParaMeth ...
- delegate, event - 里面涉及的参数类型必须完全一致,子类是不行的
public void TestF() { Test += Fun; } public void Fun(Person p) { } // 如 Person变成 SubPerson,则报错..pub ...
- ue4 delegate event
官网相关 https://docs.unrealengine.com/latest/CHN/Programming/UnrealArchitecture/Delegates/index.html wi ...
随机推荐
- git误commit大文件导致不能push问题解决
git push时终端报错: error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 Request Ent ...
- ubuntu安装nvidia显卡驱动
朋友挖矿,需要给Ubuntu(16.04版本)系统安装nvidia的显卡驱动,请我帮忙.最开始是进行手动安装.无奈的是安装完后进不了图形化界面.今天正好有时间,找了个硬盘装了个Ubuntu进行测试,成 ...
- ConcurrentHashMap为何不会出现ConcurrentModificationException异常
- Kotlin 类和对象
类定义 Kotlin 类可以包含:构造函数和初始化代码块.函数.属性.内部类.对象声明. Kotlin 中使用关键字 class 声明类,后面紧跟类名: class Runoob { // 类名为 R ...
- 《R语言入门与实践》第五章:对象改值
本章将了如何对一个数据对象中的数据进行改动,分为以下方法: 直接改值 条件取值然后改值 直接改值 单个改值:vec[1] <- 1000多个改值: vec[c(1,3,5)] <- 100 ...
- 一款好用的JS时间日期插件layDate
觉得这个插件很不错,使用起来也很方便,推荐使用 1.插件截图 2.插件配置 选择很多,配置也很简单,插件官网:https://www.layui.com/laydate/配置说得很明确,基本操作就是: ...
- SQL Pretty Printer for SSMS 很不错的SQL格式化插件
写SQL语句或者脚本时,看到凌乱的格式就头大了,于是决心找一款SQL语句格式化的工具. 功夫不负有心人还真的被我找到一款很好用,很方便的SQL Server插件:SQL Pretty Printer ...
- Node.js 8 中的 util.promisify的详解
Node.js 8带来了 很多新特性 .其中比较值得注意的,便有 util.promisify() 这个方法. util.promisify() 虽然 Promise 已经普及,但是 Node.js ...
- Shell脚本【扔一百次硬币】
#!/bin/bash#扔一百次硬币,然后分别显示出正面和反面的次数! for i in $(seq 100) do if [ `echo $((RANDOM%2))` == 0 ] then let ...
- 转载:Keytool 工具介绍
1.产生一个keystore: keytool -genkey -alias myssl -keyalg RSA -keystore myssl.jks 运行这个命令,系统提示: Enter keys ...