delegate和event的区别 (zz)
一. delegate
C#代理实际上类似于C++中的函数指针,因为C#中不存在指针,所以用代理可以完成一些原来在C++中用函数指针完成的操作,例如传递一个类A的方法m给另一个类B的对象,使得类B的对象能够调用这个方法m。但与函数指针相比,delegate有许多函数指针不具备的优点。首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。
通过以下3个步骤即可实现一个delegate:
1. 声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型。
声明一个代理的例子:
public delegate int MyDelegate(string message);
2. 创建delegate对象,并将你想要传递的函数作为参数传入。
创建代理对象的方法:
1). MyDelegate myDelegate = new MyDelegate(实例名.方法名);
2). MyDelegate myDelegate = new MyDelegate(类名.方法名);
注:如果需要代理的方法是一个static静态方法的话,采用第2种方式,否则采用第1种方式。
3. 在要实现异步调用的地方,通过上一步创建的对象来调用方法。
可以直接使用代理调用代理所指向的方法:
myDelegate(向方法传递的参数);
下面是一些需要注意的事情:
“代理”(delegate)(代表、委托):“代理”是类型安全的并且完全面向对象的。
(1)在C#中,所有的代理都是从System.Delegate类派生的(delegate是System.Delegate的别名)。
(2)代理隐含具有sealed属性,即不能用来派生新的类型。
(3)代理最大的作用就是为类的事件绑定事件处理程序。
(4)在通过代理调用函数前,必须先检查代理是否为空(null),若非空,才能调用函数。(5)在代理实例中可以封装静态的方法也可以封装实例方法。
(6)在创建代理实例时,需要传递将要映射的方法或其他代理实例以指明代理将要封装的函数原型(.NET中称为方法签名:signature)。注意,如果映射的是静态方法,传递的参数应该是类名.方法名,如果映射的是实例方法,传递的参数应该是实例名.方法名。
(7)只有当两个代理实例所映射的方法以及该方法所属的对象都相同时,才认为它们是想等的(从函数地址考虑)。
(8)多个代理实例可以形成一个代理链,System.Delegate中的定义了用来维护代理链的静态方法Combion,Remove,分别向代理链中添加代理实例和删除代理实例。
(9)代理的定义必须放在任何类的外面,如delegate int MyDelegate();而在类的方法中调用MyDelegate d = new MyDelegate(MyClass.MyMethod);来实例化自定义代理的实例。
(10)代理三步曲:
a.生成自定义代理类:delegate int MyDelegate();
b.然后实例化代理类:MyDelegate d = new MyDelegate(MyClass.MyMethod);
c.最后通过实例对象调用方法:int ret = d();
using System; public class InstanceMethod { public int Result1(string name) { Console.WriteLine("Instance:" + name); return 1; } public static void Result2() { Console.WriteLine("Static"); } } public class class1 { public delegate int DelHandler(string name); public delegate void DelHandler2(); public static DelHandler2 Del = null; public static void Main(string[] args) { InstanceMethod ins = new InstanceMethod(); DelHandler del = new DelHandler(ins.Result1); int result = del("a"); Console.WriteLine(result.ToString()); DelHandler2 del2 = new DelHandler2(InstanceMethod.Result2); del2(); Del += Display; Del(); Console.ReadLine(); } private static void Display() { Console.WriteLine("Good Night!"); } }
二:event
例1
事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用。如果其他类或结构订阅了该事件,则当发行者类引发该事件时,会调用其事件处理程序方法。
可以看出,C#中的event是通过delegate实现的,event 只是在delegate基础上作了两点限制:
1:客户只能使用委托所提供的+=和-=行为,不能直接用=,即不影响委托对其他observer的notify.
2:只有声明类可以调用(或激发)一个事件,外部类不可以直接调用其事件。
在一个C#接口中可以包容方法、C#属性以及索引器,也可以包容事件,但不能包含委托,因为接口定义的是成员,委托定义的是一个类型,而事件同样是成员。
using System; namespace CSharpConsole { //http://topic.csdn.net/t/20040903/17/3338252.html //定义委托处理程序 public delegate void PlayGameHandler(object sender, System.EventArgs e); public class 场景 { [STAThread] public static void Main(string[] args) { Console.WriteLine("场景开始了."); //生成小王 小王 w = new 小王(); //生成小账 小张 z = new 小张(); //指定监视 z.PlayGame += new PlayGameHandler(w.扣钱); //开始玩游戏 z.玩游戏(); Console.WriteLine("场景结束"); Console.ReadLine(); } } // 负责扣钱的人 public class 小王 { public 小王() { Console.WriteLine("生成小王"); } public void 扣钱(object sender, EventArgs e) { Console.WriteLine("小王:好小子,上班时间胆敢玩游戏"); Console.WriteLine("小王:看看你小子有多少钱"); 小张 f = (小张)sender; Console.WriteLine("小张的钱:" + f.钱.ToString()); Console.WriteLine("开始扣钱"); System.Threading.Thread.Sleep(500); f.钱 = f.钱 - 500; Console.WriteLine("扣完了.现在小张还剩下:" + f.钱.ToString()); } } //如果玩游戏,则引发事件 public class 小张 { //先定义一个事件,这个事件表示“小张”在玩游戏。 public event PlayGameHandler PlayGame; //保存小张钱的变量 private int m_Money; public 小张() { Console.WriteLine("生成小张."); m_Money = 1000; //构造函数,初始化小张的钱。 } public int 钱 //此属性可以操作小张的钱。 { get { return m_Money; } set { m_Money = value; } } public void 玩游戏() { Console.WriteLine("小张开始玩游戏了.."); Console.WriteLine("小张:CS好玩,哈哈哈! 我玩.."); System.Threading.Thread.Sleep(500); System.EventArgs e = new EventArgs(); OnPlayGame(e); } protected virtual void OnPlayGame(EventArgs e) { if (PlayGame != null) { PlayGame(this, e); } } } }
例2
using System; namespace nsEventSample { /// <summary> /// 类EatEventArgs必须继承自类EventArgs,用来引发事件时封装数据 /// </summary> public class EatEventArgs : EventArgs { public String restrauntName; //饭店名称 public decimal moneyOut; //准备消费金额 } /// <summary> /// 这个委托用来说明处理吃饭事件的方法的方法头(模式) /// </summary> public delegate void EatEventHandler(object sender, EatEventArgs e); /// <summary> /// 引发吃饭事件(EateEvent)的类Master(主人),这个类必须 /// 1.声明一个名为EatEvent的事件:public event EatEventHandler EatEvent; /// 2.通过一个名为OnEatEvent的方法来引发吃饭事件,给那些处理此事件的方法传数据; /// 3.说明在某种情形下引发事件呢?在饿的时候。用方法Hungrg来模拟。 /// </summary> public class Master { //声明事件 public event EatEventHandler EatEvent; //引发事件的方法 public void OnEatEvent(EatEventArgs e) { if (EatEvent != null) { EatEvent(this, e); } } //当主人饿的时候,他会指定吃饭地点和消费金额。 public void Hungry(String restrauntName, decimal moneyOut) { EatEventArgs e = new EatEventArgs(); e.restrauntName = restrauntName; e.moneyOut = moneyOut; Console.WriteLine("主人说:"); Console.WriteLine("我饿了,要去{0}吃饭,消费{1}元", e.restrauntName, e.moneyOut); //引发事件 OnEatEvent(e); } } /// <summary> /// 类Servant(仆人)有一个方法ArrangeFood(安排食物)来处理主人的吃饭事件 /// </summary> public class Servant { public void ArrangeFood(object sender, EatEventArgs e) { Console.WriteLine(); Console.WriteLine("仆人说:"); Console.WriteLine("我的主人, 您的命令是 : "); Console.WriteLine("吃饭地点 -- {0}", e.restrauntName); Console.WriteLine("准备消费 -- {0}元 ", e.moneyOut); Console.WriteLine("好的,正给您安排。。。。。。。。"); Console.WriteLine("主人,您的食物在这儿,请慢用"); } } /// <summary> /// 类God安排qinshihuang(秦始皇)的仆人是lisi(李斯),并让李斯的ArrangeFood /// 方法来处理qinshihuang的吃饭事件:qinshihuang.EatEvent += new EatEventHandler(lishi.ArrangeFood); /// </summary> public class God { public static void Main() { Master qinshihuang = new Master(); Servant lishi = new Servant(); qinshihuang.EatEvent += new EatEventHandler(lishi.ArrangeFood); //秦始皇饿了,想去希尔顿大酒店,消费5000元 qinshihuang.Hungry("希尔顿大酒店", 5000.0m); Console.ReadLine(); } } }
例3
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ConsoleApplication8 { //http://www.cnblogs.com/xinjunjie/archive/2006/08/01/464944.html public class Program { static void Main(string[] args) { Clock c = new Clock(); DisplayClock dis = new DisplayClock(); dis.Subscribe(c); c.Run(); } } public class DisplayClock { public void Subscribe(Clock clk) { clk.OnClock += new Clock.DisplayClock(clk_OnClock); } void clk_OnClock(object clock, TimerEventArgs args) { Console.WriteLine(args.hour + ":" + args.minutes + ":" + args.second); } } public class Clock { private int hour; private int minutes; private int second; public Clock() { hour = DateTime.Now.Hour; minutes = DateTime.Now.Minute; second = DateTime.Now.Second; } public delegate void DisplayClock(object clock, TimerEventArgs args); public event DisplayClock OnClock; public void Run() { while (1 == 1) { Thread.Sleep(100); DateTime dt = DateTime.Now; if (dt.Second != second) { if (OnClock != null) { OnClock(this, new TimerEventArgs(hour, minutes, second)); } } hour = DateTime.Now.Hour; minutes = DateTime.Now.Minute; second = DateTime.Now.Second; } } } public class TimerEventArgs : EventArgs { public readonly int hour; public readonly int minutes; public readonly int second; public TimerEventArgs(int h, int m, int s) { this.hour = h; this.minutes = m; this.second = s; } } }
例4
using System; using System.Collections.Generic; using System.Text; namespace UsingEvent { public delegate void ClickEventHandler(object sender, EventArgs e);//声明一个代表:请看文章最后面Note public class MyButton //创建MyBottom { public event ClickEventHandler ClickEvent;//声明一个事件 public void Click() //单击MyButton { if (ClickEvent != null) { Console.WriteLine("MyButton: 我被单击了"); ClickEvent(this, null); //抛出事件,给所有相应者 } } } public class MyForm { public MyButton myButton = new MyButton(); public MyForm() { //添加事件到myButton中,当myButton被单击的时候就会调用相应的处理函数 myButton.ClickEvent += new ClickEventHandler(OnClickEvent); } //事件处理函数 void OnClickEvent(object sender, EventArgs e) { Console.WriteLine("MyForm: 我知道你被单击了!"); } } class Program { static void Main(string[] args) { MyForm form = new MyForm();//生成一个MyForm form.myButton.Click();//单击MyForm中的鼠标,效果就出来了 Console.ReadLine(); } } }