多播委托与事件
1.多播委托定义以及使用
我们通过委托可以实现把方法作为参数,传递给委托执行。同样,我们的委托也可以依次执行多个方法,此时就需要我们的多播委托了。
没有接触多播委托之前,我们调用多个方法的委托定义如下:
ReturnWithPara para = new ReturnWithPara(ShowId); //当前类的方法 ReturnWithPara para1 = ShowId;//当前类的方法 ReturnWithPara para2 = (i, name) => Console.WriteLine("编号:" + i + ",名称:" + name);//lamda表达式 ReturnWithPara para3 = new OtherClass().ShowId;//外部类的实例方法 ReturnWithPara para4 = OtherClass.ShowIdStatic;//外部类的静态方法
当使用多播委托后,我们的代码如下:
//多播委托方式:通过+=添加方法,形成方法链,并按顺序执行 ReturnWithPara para = new ReturnWithPara(ShowId); //当前类的方法 para += ShowId; para += (i, name) => Console.WriteLine("编号:" + i + ",名称:" + name);//lamda表达式 para += new OtherClass().ShowId;//外部类的实例方法 para += OtherClass.ShowIdStatic;//外部类的静态方法 para.Invoke(21, "张三");
多播委托方式:通过+=添加方法,形成方法链,Invoke时按添加顺序执行方法.
同样,我们也可以通过-=给委托实例移除方法,如下所示
//多播委托移除:通过-=给委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的移除,并且只移除一次 para -= ShowId; para -= (i, name) => Console.WriteLine("编号:" + i + ",名称:" + name);//lamda表达式 para -= new OtherClass().ShowId;//外部类的实例方法 para -= OtherClass.ShowIdStatic;//外部类的静态方法 para.Invoke(22, "李四");
注意:多播委托移除:通过-=给委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的移除,并且只移除一次
以上代码执行后,我们会发现(i, name) => Console.WriteLine("编号:" + i + ",名称:" + name);和new OtherClass().ShowId;没有移除成功。
这是因为:1.每个lamda表达式在编译时都会生成一个不同的方法,但都属于同一个实例。因此lamda表达式不能移除,所以不推荐lamda表达式写法
2.new OtherClass().ShowId不能移除是因为是两个不同的匿名对象,所以不能移除
多播委托不能直接调用异步方法(eg:para.BeginInvoke(22,"李四");),如果要使用异步调用,可以采取下面的方法
ReturnWithPara para = new ReturnWithPara(ShowId); //当前类的方法 para += ShowId; para += (i, name) => Console.WriteLine("编号:" + i + ",名称:" + name);//lamda表达式 para += new OtherClass().ShowId;//外部类的实例方法 para += OtherClass.ShowIdStatic;//外部类的静态方法 foreach (var item in para.GetInvocationList()) { para.BeginInvoke(21, "张三", null, item); }
在上述代码中,我们通过para.GetInvocationList()方法获取所有的执行方法,循环执行每个的异步方法。
注意:由于多播委托执行带返回值的多播委托时,只返回最后一个方法的返回值,所以多播委托不适用于带返回值的委托。
多播委托常用的场景就是观察者模式。如下代码所示
我们首先定义个Cat,Chicken等实体,每个实体都有一个方法事件。Cat的Miao方法会引起连锁反应,会引起鸡叫,baby哭泣等等,Cat类里面定义了个委托Cathander
public class Cat { public void Miao() { Console.WriteLine("Miao····"); if (Cathander != null) { Cathander.Invoke(); } } public Action Cathander; }
在程序的上级调用中如下所示:
Cat cat = new Cat(); cat.Cathander += new Chicken().Wo; cat.Cathander += new Dog().Wang; cat.Cathander += new Baby().Cry; cat.Cathander += new Father().Roar; cat.Cathander += new Brother().Turn; cat.Cathander += new Mother().Run; cat.Miao();
cat.Miao()执行后就会调用绑定了事件的多播委托Cathander
使用观察者设计模式的好处:保证了Cat类的稳定,需求变更时,只需修改最上层调用即可,无需关注cat代码
2.事件
定义:多播委托的定义前面加一个event关键字,如下所示,我们子啊Cat里面定义了一个事件CathanderEvent
public event Action CathanderEvent; public void MiaoEvent() { Console.WriteLine("Miao····"); if (CathanderEvent != null) { CathanderEvent.Invoke(); } }
上层代买调用执行:
Cat cat = new Cat(); cat.CathanderEvent += new Chicken().Wo; cat.CathanderEvent += new Dog().Wang; cat.CathanderEvent += new Baby().Cry; cat.CathanderEvent += new Father().Roar; cat.CathanderEvent += new Brother().Turn; cat.CathanderEvent += new Mother().Run; cat.MiaoEvent();
观察以上代码,和多播委托很类似。
3.多播委托与事件的区别和联系
从以上部分介绍,我们是否感觉多播委托和事件很类似,那为什么还要用事件呢?
事件加了权限限制,在事件定义的外部类(或定义类的子类)中,只允许外部+=和-=。不能进行类似cat.CathanderEvent=null之类的赋值,这种赋值只能在定义事件的内部类中才可以。
委托与事件的区别和联系:委托是一个类,而事件是委托的一个实体对象
4.事件的运用场景
事件应用很广泛,主要包含:
1.服务器的控件的点击事件、下拉事件等。
2、Webform页面声明周期,各种页面级事件(pre_init、page_load、application_start)。
3.请求级事件:经常注册module 等等