.NET delegate 委托 、 Event 事件,接口回调
// 定义委托
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度。
// 省略 ...