首页 > 代码库 > 委托与事件
委托与事件
1. 如何实现自定义事件
A.定义自定义事件信息,继承自EventArgs
public class BoiledEventAgs:EventArgs { public readonly int tempreture; public BoiledEventAgs(int tem) { this.tempreture = tem; } } |
B.定义委托
public delegate void BoilHandler(object sender,BoiledEventAgs e); |
C. 在类的内部定义事件,类型为自定义的委托类型BoilHandler
public event BoilHandler Boiled; |
D. 设置监视方法供内部调用
private void OnBoiled(BoiledEventAgs e) { if (Boiled != null) Boiled(this, e); } |
E. 可能触发事件执行的方法
public void BoilWater() { for (int i = 0; i <= 100; i++) { int Tempreture = i; if (Tempreture >= 95) { BoiledEventAgs e = new BoiledEventAgs(Tempreture); OnBoiled(e); } } } |
F. 写出委托类型的方法,返回值与参数类型与委托定义的一致
public class Alarm { public void MakeAlert(object sender, BoiledEventAgs e) { Heater h = sender as Heater; Console.WriteLine("Alerm:{0} {1}!", h.Type, h.Price); Console.WriteLine("水?温?{0},ê?水?要°a烧|?开a了¢?", e.tempreture); } } public class Display { public static void ShowMsg(object sender, BoiledEventAgs e) { Console.WriteLine("显?示o?器??:水?温?{0}", e.tempreture); } } |
G.绑定事件
Heater h = new Heater("Inspur", 0, 2999); h.Boiled += new Alarm().MakeAlert; h.Boiled += Display.ShowMsg; h.BoilWater(); |
全部代码
namespace DelegateDemo { class Program { static void Main(string[] args) { Heater h = new Heater("Inspur", 0, 2999); h.Boiled += new Alarm().MakeAlert; h.Boiled += Display.ShowMsg; h.BoilWater(); } } public delegate void BoilHandler(object sender,BoiledEventAgs e); public class BoiledEventAgs:EventArgs { public readonly int tempreture; public BoiledEventAgs(int tem) { this.tempreture = tem; } } public class Heater { public string Type { private set; get; } public int Tempreture { private set; get; } public double Price { private set; get; } public Heater(string type,int tempreture,double price) { this.Type = type; this.Tempreture = tempreture; this.Price = price; } public event BoilHandler Boiled; private void OnBoiled(BoiledEventAgs e) { if (Boiled != null) Boiled(this, e); } public void BoilWater() { for (int i = 0; i <= 100; i++) { int Tempreture = i; if (Tempreture >= 95) { BoiledEventAgs e = new BoiledEventAgs(Tempreture); OnBoiled(e); } } } } public class Alarm { public void MakeAlert(object sender, BoiledEventAgs e) { Heater h = sender as Heater; Console.WriteLine("Alerm:{0} {1}!", h.Type, h.Price); Console.WriteLine("水?温?{0},ê?水?要°a烧|?开a了¢?", e.tempreture); } } public class Display { public static void ShowMsg(object sender, BoiledEventAgs e) { Console.WriteLine("显?示o?器??:水?温?{0}", e.tempreture); } } } |
2. 使用事件访问器让事件只允许一个客户订阅
我们经常看到属性有set;get访问器,事件也有事件访问器add;remove
此时我们只需要定义一个私有的委托类型的变量,注意不需要添加event关键字限制,然后定义一个事件变量,使用add remove访问器为这个委托类型添加或者删除订阅者
private BoilHandler Boiled; public event BoilHandler boiled { add { Boiled += value; } remove { Boiled -= value; } } |
使用Boiled += value;则仍然允许多个订阅者,修改为Boiled = value;则每次会覆盖之前的绑定事件实现只允许一个客户订阅;
此时添加或者删除事件为
Heater h = new Heater("Inspur", 0, 2999); h.boiled += new Alarm().MakeAlert; h.boiled += Display.ShowMsg; h.BoilWater(); 修改后的输出只有第二个绑定事件有效 |
3.获取多个返回值与异常处理
委托链生成方式
Delegate有两个静态方法Combine()和Remove()方法,分别表示向委托链中添加或删除一个委托
BoilHandler b1 = new BoilHandler(new Alarm().MakeAlert); b1 = Delegate.Combine(b1,new BoilHandler(Display.ShowMsg)) as BoilHandler;
//如果b1为null也无影响Combine方法会返回后面的新的委托对象
b1 = Delegate.Remove(b1, new BoilHandler(Display.ShowMsg)) as BoilHandler;
需要注意的是Remove每次只会移除一个委托实例,不会删除所有的匹配对象(假设加入了多个相同的委托实例);
BoilHandler b1 = new BoilHandler(new Alarm().MakeAlert); int i1 = b1.GetInvocationList().Length; Console.WriteLine(i1); b1 = Delegate.Combine(b1,new BoilHandler(Display.ShowMsg)) as BoilHandler; int i2 = b1.GetInvocationList().Length; Console.WriteLine(i2); b1 = Delegate.Combine(b1, new BoilHandler(Display.ShowMsg)) as BoilHandler; int i3 = b1.GetInvocationList().Length; Console.WriteLine(i3); b1 = Delegate.Remove(b1, new BoilHandler(Display.ShowMsg)) as BoilHandler; int i4 = b1.GetInvocationList().Length; Console.WriteLine(i4);
返回结果为
委托链中存在两个new BoilHandler(Display.ShowMsg)委托实例,调用一次remove方法只能移除一个实例
至于委托的+= 与 -=只是.NET为我们提供的重载符而已;
常见的委托都是返回值为void的方法作为委托对象,这是因为如果我们为方法定义了返回值则委托链表中后面的方法返回值会将之前的返回值覆盖掉;修改上面的例子,将MakeAlert()方法与ShowMsg()方法的返回值修改为string类型的(定义委托时需要定义返回值为string类型),观察我们执行事件之后的返回值;
class Program { static void Main(string[] args) { Heater h = new Heater("Inspur", 0, 2999); h.Boiled += new Alarm().MakeAlert; h.Boiled += Display.ShowMsg; h.BoilWater(); } } public delegate string BoilHandler(object sender,BoiledEventAgs e); public class BoiledEventAgs:EventArgs { public readonly int tempreture; public BoiledEventAgs(int tem) { this.tempreture = tem; } } public class Heater { public string Type { private set; get; } public int Tempreture { private set; get; } public double Price { private set; get; } public Heater(string type,int tempreture,double price) { this.Type = type; this.Tempreture = tempreture; this.Price = price; } public BoilHandler Boiled; private void OnBoiled(BoiledEventAgs e) { if (Boiled != null) { string str = Boiled(this, e); //打印委托返回值 Console.WriteLine(str); } } public void BoilWater() { for (int i = 0; i <= 100; i++) { int Tempreture = i; if (Tempreture >= 95) { BoiledEventAgs e = new BoiledEventAgs(Tempreture); OnBoiled(e); } } } } public class Alarm { public string MakeAlert(object sender, BoiledEventAgs e) { Heater h = sender as Heater; Console.WriteLine("Alerm:{0} {1}!", h.Type, h.Price); Console.WriteLine("水温{0},水要烧开了", e.tempreture); return "这是一个警报器"; } } public class Display { public static string ShowMsg(object sender, BoiledEventAgs e) { Console.WriteLine("显示器:水温{0}", e.tempreture); return "这是一个显示器"; } }
此时我们看到打印结果为
只显示了显示器的信息(也有可能只显示警报器的信息,因为根据.net解释委托链的执行顺序是不一定的),但是结果说明后执行的返回值 会覆盖先执行的返回值。
那么如何获取委托链的所有返回值呢?自定义委托类型在编译后继承自MuticastDelegate类,而MuticastDelegate类继承自Delegate,在Delegate内部维护了一个委托链表,链表上的每隔元素只包含一个目标方法的委托对象。而通过Delegate基类的GetInvocationList()可以获得委托链表,然后遍历链表,通过链表中的每个委托对象来调用方法就可以获取所有委托的返回值;修改上面的代码
Delegate[] dels = Boiled.GetInvocationList(); List<string> strs = new List<string>(); foreach (var del in dels) { BoilHandler method = del as BoilHandler; strs.Add(method(this,e)); } //打印所有委托的返回值 foreach (string str in strs) { Console.WriteLine(str); }
通过此种方法可获取委托的所有返回值,但看起来好像没有什么意义。此时我们想如果委托链中其中的一个方法出异常了呢?面对一场我们通常的做法是使用try catch进行捕获,让我们尝试一下吧
class Program { static void Main(string[] args) { Heater h = new Heater("Inspur", 0, 2999); h.Boiled += new Alarm().MakeAlert; h.Boiled += Display.ShowMsg; h.BoilWater(); } } public delegate void BoilHandler(object sender,BoiledEventAgs e); public class BoiledEventAgs:EventArgs { public readonly int tempreture; public BoiledEventAgs(int tem) { this.tempreture = tem; } } public class Heater { public string Type { private set; get; } public int Tempreture { private set; get; } public double Price { private set; get; } public Heater(string type,int tempreture,double price) { this.Type = type; this.Tempreture = tempreture; this.Price = price; } public event BoilHandler Boiled; private void OnBoiled(BoiledEventAgs e) { if (Boiled != null) { //尝试捕获异常 try { Boiled(this, e); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } public void BoilWater() { List<string> list = new List<string>(); for (int i = 0; i <= 100; i++) { int Tempreture = i; if (Tempreture >= 95) { BoiledEventAgs e = new BoiledEventAgs(Tempreture); OnBoiled(e); } } } } public class Alarm { public void MakeAlert(object sender, BoiledEventAgs e) { //抛出一个异常 throw new Exception("警报器坏掉了!"); //Heater h = sender as Heater; //Console.WriteLine("Alerm:{0} {1}!", h.Type, h.Price); //Console.WriteLine("水温{0},水要烧开了", e.tempreture); } } public class Display { public static void ShowMsg(object sender, BoiledEventAgs e) { Console.WriteLine("显示器:水温{0}", e.tempreture); } }
此时看执行结果
我们发现其中一个委托发生异常之后会直接影响到后面委托的执行!此时我们可以借鉴返回所有委托返回值的处理方式处理异常修改上面的代码
private void OnBoiled(BoiledEventAgs e) { if (Boiled != null) { Delegate[] dels = Boiled.GetInvocationList(); foreach (var del in dels) { BoilHandler method = del as BoilHandler; //尝试捕获异常 try { method(this, e); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } }
此时观察结果可看到一个委托实例引发的异常不再影响其他委托的执行
4.委托的协变与逆变
首先协变与逆变只针对引用类型,不能用于值类型或void,此特性是利用多态实现的;引用类型在委托链中以指针的方式保存,值类型保存在堆栈上没有指针的引用一说。
协变就是返回类型可以是原委托定义的返回类型的派生类型;
逆变是指参数类型可以使原委托定义的参数类型的基类型
public object MyCallBack(FileStream fs); string SomeMethod(Stream s);
Somemethod方法返回类型为string,是object的派生类型,此为协变;参数类型为FileStream的父类型,此为逆变;我们可以将SomeMethod方法绑定到MyCallback委托上面。
5.委托解密
首先定义一个委托
public delegate void BoilHandler(object sender,BoiledEventAgs e);
利用IL DASM工具查看生成代码
可看到它继承自MulticastDelegate(MuticastDelgate继承自Delegate);生成了四个方法 构造函数、Invoke、BeginInvoke、EndInvoke方法;
Delgate有3个比较重要的私有字段
1.Object类型的target 用来保存回调方法要操作的对象,如果回调方法为静态方法则为null;
2.IntPtr类型的methodPtr 内部整数值,CLR用它标识要回调的方法
3.object类型的invocationList 该字段通常为null。构造一个委托链时,它可以引用一个委托数组
创建一个委托实例的过程如下:
C#编译器知道要构造一个委托实例,此时它会分析源码来确定引用的是哪个对象和方法。
对象引用被传递给构造器的object参数,标识方法的特殊IntPtr值被传递给构造器的method参数(静态方法时object则为null)
同时构造器将invocationList的字段设置为null
所以每个委托对象实际都是一个包装器,其中包装了方法和调用该方法要操作的那个对象;
此时我们为BoilHandler绑定方法
BoilHandler b1 = new BoilHandler(new Alarm().MakeAlert);
BoilHandler b2 = new BoilHandler(Display.ShowMsg);
表现形式如下:
此时我们可以利用Targe属性和Method属性获取并验证回调方法的对象类型和方法签名
b1.Target.ToString();
b1.Method.ToString();
我们也可以再执行委托的时候直接使用Invoke方法
b1.Invoke(new Heater("Micro",0,1200),new BoiledEventAgs(21)); //等同于下
b1(new Heater("Micro", 0, 1200), new BoiledEventAgs(21));
6.泛型委托
Action<T>委托:提供1-16个参数的返回值为void类型的泛型委托
Func<T,TResult>委托:提供1-16个参数的返回值为TResult类型的委托
NOTE:如果需要为泛型指定约束条件或者需要使用ref out关键字都需要手动定义委托,使用Action Func委托可以简化代码的书写减少系统中类型个数
7.委托的语法糖,各版本写法变化
//C# 1.1 MyDelegate m = new MyDelegate(isOk); //C# 2.0 匿名方法 , 可以写内联语句,不必创建一个方法 MyDelegate m2 = delegate(string s, string s2) { Console.WriteLine(s + "," + s2); }; //C# 3.0 Lambda, 使用参数x,y ; x、y的类型由编译器推测 MyDelegate m3 = (x, y) => { Console.WriteLine(x + "," + y); };
引用与摘抄
.NET之美 张子阳 http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html
CLR via C#
委托知识点总结
委托是事件的基础
委托的CLR生成原理
用委托链回调许多方法处理方法异常
泛型委托
常用委托语法糖
委托与反射
……
(最恨面试官问,请简单介绍一下委托。。。。。这让俺从何简单说起)