// 定义委托
delegate void WorkDone();
class Program
{
static void Main(string[] args)
{
Do();
Console.ReadLine();
}
public static void Do()
{
// 首先给callback委托赋值
WorkDone callback = new WorkDone(WorkDoneHandler);
// 将callback作为参数
Working(callback);
}
public static void Working(WorkDone callBack)
{
// 当工作完成的时候执行这个委托
callBack();
}
public static void WorkDoneHandler()
{
Console.WriteLine(DateTime.Now);
}
}
/*上面的代码中,将方法WorkDoneHandler()作为参数,传递给了另一个方法Working(WorkDone callBack),这样做的好处在于,可以动态的指定执行哪个方法。比如在Do()方法中,我们指定的callback 是WorkDoneHandler 当然也可以是其它匹配的方法。而Working()方法根本不需要知道自己最后执行的是哪个Handler。*/
二、 接口回调
通常情况下,我们创建一个对象,并马上直接去使用它的方法。然而,在有些情况下,希望能在某个场景出现后或条件满足时才调用此对象的方法。回调就可以解决这个“延迟调用对象方法”的问题。这个被调用方法的对象称为回调对象。
实现回调的原理简介如下:
首先创建一个回调对象,然后再创建一个控制器对象,将回调对象需要被调用的方法告诉控制器对象。控制器对象负责检查某个场景是否出现或某个条件是否满足。当此场景出现或此条件满足时,自动调用回调对象的方法。
以下为C#实现回调的一个小例子:
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//创建一个控制器对象,将提供给它的回调对象传入
Controller obj = new Controller(new CallBack());
//启动
obj.Star();
}
}
public interface IBack
{
void run();
}
public class CallBack : IBack
{
public void run()
{
//为了简洁这里只是显示下时间
System.Console.WriteLine(DateTime.Now);
}
}
public class Controller
{
public IBack CallBackObj = null; //这里引用回调对象
public Controller(IBack obj)
{
this.CallBackObj = obj;
}
public void Star()
{
Console.WriteLine("敲键盘任意键就显示当前的时间,直到按ESC退出....");
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
{
CallBackObj.run();
}
}
}
}
可以看到,当示例程序运行时,何时调用CallBack对象的run()方法是由用户决定的,用户每敲一个键,控制器对象就调用一次CallBack的run()方法。这个示例中实现回凋的关键在于IBack接口的引入。
如果不用IBack接口,而直接使用 CallBack对象,一样可以实现同样的效果,如下:
public class Controller
{
public CallBack CallBackObj = null; //回调对象方法的引用
public Controller(CallBack obj)
{
this.CallBackObj = obj;
}
public void Star()
{
Console.WriteLine("敲键盘任意键就显示当前的时间,直到按ESC退出....");
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
{
CallBackObj.run();
}
}
}
但仔细思考,这样做的结果就使Controller类与CallBack对象绑定在一起,万一如果需要调用其他类型的对象,则必须修改Controller类的代码。
如果Controller类接收的是一个抽象的接口变量Iback,则任何实现了该接口的对象都可以被Controller类对象所回调,Controller类的代码就再不用被修改,保证了代码对环境的适应性,无疑是一个很好的解决方案。
通过加入event关键字,在编译的时候编译器会自动针对事件生成一个私有的字段(与此事件相关的委托),以及add_xxx和remove_xxx两个访问器方法。其它类只能+=、-= 操作,不能直接 xx.eventName;
1.委托允许直接通过委托去访问相应的处理函数,而事件只能通过公布的回调函数去调用
2.事件只能通过“+=”,“-=”方式注册和取消订户处理函数,而委托除此之外还可以使用“=”直接赋值处理函数。

.NET 编译成
private event delegate evntname;
public event delegate EventName{
add__xxx 用+= 调用
remove_xxx 用-=调用
}
//定义委托,它定义了可以代表的方法的类型
public delegate void GreetingDelegate(string name);
class Program
{
private static void EnglishGreeting(string name)
{
Console.WriteLine("Morning, " + name);
}
private static void ChineseGreeting(string name)
{
Console.WriteLine("早上好, " + name);
}
//注意此方法,它接受一个GreetingDelegate类型的方法作为参数
private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
MakeGreeting(name);
}
static void Main(string[] args)
{
GreetPeople("XXXXXX", EnglishGreeting);
GreetPeople("YYYYY", ChineseGreeting);
Console.ReadLine();
GreetingDelegate delegate1;
// 先给委托类型的变量赋值
delegate1 = EnglishGreeting; //等价于delegate1=new GreetingDelegate(EnglishGreeting)
// 给此委托变量再绑定一个方法
delegate1 += ChineseGreeting;
//等价于------------------------
//GreetingDelegate delegate1 = new GreetingDelegate (EnglishGreeting);
//delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
//--------------------------------------
// 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
GreetPeople("XXXX", delegate1);
Console.ReadKey();
注意这里,第一次用的“=”,是赋值的语法;
第二次,用的是“+=”,是绑定的语 法。
如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。
//-------------------------------------------
GreetingDelegate delegate1 = new GreetingDelegate();
delegate1 += EnglishGreeting; // 这次用的是 “+=”,绑定语法。
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
但实际上,这样会出现编译错误: “GreetingDelegate”方法没有采用“0”个参数 的重载。
尽管这样的结果让我们觉得有点沮丧,但是编译的提示:“没有0个参数的重 载”再次让我们联想到了类的构造函数。
但事件就可以了。
//------------------------------------------------------------------
}
}
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数 来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else (Switch)语句,同时使得程序具有更好的可扩展性。
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调
用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
事件 其实没什么不好理解的,声明一个事件不过类似于声明一个进行了封装的委托类型 的变量而已。
在类的内部,不管你声明它是public还是protected,它总是private的。
在类的外部,注册“+=”和注 销“-=”的访问限定符与你在声明事件时使用的访问符相同。
public class GreetingManager{
//这一次我们在这里声明一个事件
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name) {
MakeGreet(name);
}
}
static void Main(string[] args) {
GreetingManager gm = new GreetingManager();
//实际上尽管我们在GreetingManager里将 MakeGreet 声明为public,但是,实际上MakeGreet会被编译成私有字段,
gm.MakeGreet = EnglishGreeting; // 编译错误1 +=,-= 编译成功
gm.MakeGreet += ChineseGreeting;
gm.GreetPeople("Jimmy Zhang");
}

现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委 托,只不过不管是不是声明为public,它总是被声明为private。另外,它还有两个 方法,分别是add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委 托类型的方法和取消注册。实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应 remove_MakeGreet。而这两个方法的访问限制取决于声明事件时的访问限制符。
namespace Delegate {
class Heater
{
private int temperature; // 水温
// 烧水
public void BoilWater() {
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{
MakeAlert(temperature);
ShowMsg(temperature);
}
}
}
// 发出语音警报
private void MakeAlert(int param) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param);
}
// 显示水温
private void ShowMsg(int param) {
Console.WriteLine("Display:水快开了,当前温度:{0}度。" , param);
}
}
class Program {
static void Main()
{
Heater ht = new Heater();
ht.BoilWater();
}
}
}

namespace Delegate {
// 热水器
public class Heater {
private int temperature;
public delegate void BoilHandler(int param); //声明委托
public event BoilHandler BoilEvent;//声明事件
// 烧水
public void BoilWater() {
for (int i = 0; i <= 100; i++) {
temperature = i;
if (temperature > 95) {
if (BoilEvent != null) { //如果有对象注册
BoilEvent(temperature); //调用所有注册对象的方法
}
}
}
}
}
// 警报器
public class Alarm {
public void MakeAlert(int param) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:",param);
}
}
// 显示器
public class Display {
public static void ShowMsg(int param) {
//静态方法
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
}
}
class Program {
static void Main() {
Heater heater = new Heater();
Alarm alarm = new Alarm();
heater.BoilEvent += alarm.MakeAlert; //注册方法
heater.BoilEvent += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.BoilEvent += Display.ShowMsg; //注册静态方法
heater.BoilWater();
//烧水,会自动调用注册过对象的方 法
}
}

using System;
using System.Collections.Generic;
using System.Text;
namespace Delegate {
// 热水器
public class Heater {
private int temperature;
public string type = "RealFire 001"; // 添加型号作为演示
public string area = "China Xian"; // 添加产地作为演示
//声明委托
public delegate void BoiledEventHandler(Object sender,BoiledEventArgs e);
public event BoiledEventHandler Boiled; //声明事件
// 定义BoiledEventArgs类,传递给Observer所感兴趣的信息 //事件参数
public class BoiledEventArgs : EventArgs {
public readonly int temperature;
public BoiledEventArgs(int temperature) {
this.temperature = temperature;
}
}
// 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
protected virtual void OnBoiled(BoiledEventArgs e) {
if (Boiled != null) { // 如果有对象注册
Boiled(this, e); // 调用所有注册对象的方法
}
}
// 烧水。
public void BoilWater() {
for (int i = 0; i <= 100; i++) {
temperature = i;
if (temperature > 95) {
//建立BoiledEventArgs 对象。
BoiledEventArgs e = new BoiledEventArgs(temperature);
OnBoiled(e); // 调用 OnBolied方法
}
}
}
}
//---------
// 警报器
public class Alarm {
public void MakeAlert(Object sender,Heater.BoiledEventArgs e)
{
Heater heater = (Heater)sender; //这里是不是很熟悉呢?
//访问 sender 中的公共字段
Console.WriteLine("Alarm:{0} - {1}: ", heater.area,heater.type);
Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:",e.temperature);
Console.WriteLine();
}
}
// 显示器
public class Display {
public static void ShowMsg(Object sender,Heater.BoiledEventArgs e) {
//静态方法
Heater heater = (Heater)sender;
Console.WriteLine("Display:{0} - {1}: ",heater.area, heater.type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
Console.WriteLine();
}
}
class Program {
static void Main() {
Heater heater = new Heater();
Alarm alarm = new Alarm();
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += (new Alarm()).MakeAlert;//给匿名对象注册方法
heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方 法
}
}
}
输出为:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:96度。
// 省略 ...
事件场景:
猫叫->老鼠逃跑 & 主人惊醒
这是一个典型的观察者模式的应用场景,事件的发源在于猫叫这个动作,在猫叫之后,老鼠开始逃跑,而主人则会从睡梦中惊醒。可以发现,主人和老鼠这两个类型的动作相互之间没有联系,但都是由猫叫这一事件触发的。
设计的大致思路在于,猫类包含并维护一个猫叫的动作,主人和老鼠的对象实例需要订阅猫叫这一事件,保证猫叫这一事件发生时主人和老鼠可以执行相应的动作。
(1)设计猫类,为其定义一个猫叫的事件CatCryEvent:
public class Cat
{
private string name;
// 猫叫的事件
public event EventHandler CatCryEvent;
public Cat(string name)
{
this.name = name;
}
// 触发猫叫事件
public void CatCry()
{
// 初始化事件参数
CatCryEventArgs args = new CatCryEventArgs(name);
Console.WriteLine(args);
// 开始触发事件
CatCryEvent(this, args);
}
}
public class CatCryEventArgs : EventArgs
{
private string catName;
public CatCryEventArgs(string catName)
: base()
{
this.catName = catName;
}
public override string ToString()
{
string message = string.Format("{0}叫了", catName);
return message;
}
}
(2)设计老鼠类,在其构造方法中订阅猫叫事件,并提供对应的处理方法
public class Mouse
{
private string name;
// 在构造方法中订阅事件
public Mouse(string name, Cat cat)
{
this.name = name;
cat.CatCryEvent += CatCryEventHandler;
}
// 猫叫的处理方法
private void CatCryEventHandler(object sender, CatCryEventArgs e)
{
Run();
}
// 逃跑方法
private void Run()
{
Console.WriteLine("{0}逃走了:我勒个去,赶紧跑啊!", name);
}
}
(3)设计主人类,在其构造犯法中订阅猫叫事件,并提供对应的处理方法
public class Master
{
private string name;
// 在构造方法中订阅事件
public Master(string name, Cat cat)
{
this.name = name;
cat.CatCryEvent += CatCryEventHandler;
}
// 针对猫叫的处理方法
private void CatCryEventHandler(object sender, CatCryEventArgs e)
{
WakeUp();
}
// 具体的处理方法——惊醒
private void WakeUp()
{
Console.WriteLine("{0}醒了:我勒个去,叫个锤子!", name);
}
}
(4)最后在Main方法中进行场景的模拟:
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat("假老练");
Mouse mouse1 = new Mouse("风车车", cat);
Mouse mouse2 = new Mouse("米奇妙", cat);
Master master = new Master("李扯火", cat);
// 毛开始叫了,老鼠和主人有不同的反应
cat.CatCry();
Console.ReadKey();
}
}
这里定义了一只猫,两只老鼠与一个主人,当猫的CatCry方法被执行到时,会触发猫叫事件CatCryEvent,此时就会通知所有这一事件的订阅者。本场景的关键之处就在于主人和老鼠的动作应该完全由猫叫来触发。下面是场景模拟代码的运行结果:

总结:
事件Event本质也是委托,只是特殊的委托
[Serializable]
public delegate void EventHandler(object sender, TEventArgs e);
该委托没有返回值,并且有两个参数:一个事件源和一个事件参数。而当事件的使用者订阅该事件时,其本质就是将事件的处理方法加入到委托链之中。事件是一个特殊的委托实例,提供了两个供订阅事件和取消订阅的方法:add_event和remove_event,其本质都是基于委托链来实现。
事件比委托应用的场景更广
一个窗体A中定义一个事件,一般都是public、protect,不能私有的,私有,外部不能访问,不能为其注册方法,就是没有任何意义了。
另外一个窗体B为其给上面一个窗体A中的事件注册方法【方法可以在上面的窗体A中,也是窗体B中,甚至其它地方的方法】,
窗体B中,可以触发窗体A中的事件{间接触发,之家触发,上面例子就是间接触发。}。