【温故知新】C#委托delegate
在c#的学习过程中,学到委托与事件总会迷糊一段时间,迷糊过后自然而就似懂非懂了~,所以最近我打算把以前所学的迷糊过的知识总结,温故知新,总结记录下来。
首先,我们来看一下msdn对委托的定义:
delegate 关键字用于声明可用来封装命名方法的引用类型。委托大致类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。
delegate 可让您传递一个函数作为参数。委托的类型安全要求作为 delegate 传递的函数具有与 delegate 声明相同的签名。
委托是事件的基础。
我们都知道,c++中的函数是可以通过函数指针来当做参数使用的,c#中没有指针这种东西,怎么办?所以弄出一个delegate出来。
但与函数指针相比,delegate有许多函数指针不具备的优点。 首先,函数指针只能指向静态函数,而delegate既可以引用静态函数,又可以引用非静态成员函数。
在引用非静态成员函数时,delegate不但保存了对此函数入口指针的引用,而且还保存了调用此函数的类实例的引用。 其次,与函数指针相比,delegate是面向对象、类型安全、可靠的受控(managed)对象。
也就是说,runtime能够保证delegate指向一个有效的方法,你无须担心delegate会指向无效地址或者越界地址。
接下来,我们看看c#代码如何声明、实例化、使用一个委托:
private delegate void MyDelegate(string msg); static void Notify(string name) { Console.WriteLine("Notification received for: {0}", name); } MyDelegate mydelegate=new MyDelegate(Notify); mydelegate("yoyoyo!");
其中要注意的是:
1、委托不能被定义为static的。
2、返回类型,函数参数一定要和被委托函数一样(继承时)。
3、委托可以链接在一起;例如,可以对一个事件调用多个方法。通过+=,-=操作。
看到这里,我们大致知道“委托”是个什么东西了,但是,相信初学者还有一大堆疑问,委托具体到底是拿来干什么的呢???写程序的时候怎么用它才合适?这些问题也困扰了我许久。
从字面意思看“委托”:把事情托付给别人或别的机构办理,举日常生活中例子:
中午午餐时间,由于工作还没做完,所以叫同事帮带一份午餐,这是一种“委托”。
玩游戏升级太慢,哪有那么多时间升级,果断找人帮代练~又快又舒心~满级之后再把号给我,这也是一种“委托”。
总之,就是在你忙的时候叫其他人帮你做事,这让我们想到了什么?-----异步编程!,例如在做一些耗时操作时,在不影响主线程情况下异步,特别是UI线程,声明一个委托然后BeginInvoke,帮我做事吧~在大多数情况下,就是用来在异步编程中充当“跑腿的”。
在这里我们就不讨论异步编程,也不涉及异步编程,否则就扯远了,下面就让我们通过实例体会delegate的基本作用吧。
有这么一个场景:
早恋是许多家长关心的问题啊,最近家长A貌似发现自己的孩子LiLei对家长B的孩子HanMeiMei有意思,同时家长B也发现了,为了确认情况,双方父母私下委托老师发现真实情况的时候通知一声,提早纠正学生的观念~。
简单代码模拟场景:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Delegate { class Program { static void Main(string[] args) { //家长A,B,他们不可能一天去学校监视孩子吧?委托给老师咯,自己还要去上班呢 Parent pa = new Parent(); Parent pb = new Parent(); //家长A,B分别委托老师发现早恋情况时通知他们 Teacher teacher = new Teacher(); teacher.NotifyStudentLove = pa.ReceiveMsg; teacher.NotifyStudentLove += pb.ReceiveMsg; //老师开始检查早恋情况 teacher.CheckLove(); } } }
老师类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Delegate { public class Teacher { //声明一个委托类型,通知家长 public delegate void NotifyDelegate(string msg); //老师被吩咐了1个委托 //声明委托:在发现早恋时时通知家长 private NotifyDelegate NotifyStudentLove; //如果还想委托老师发现学生玩手机的时候通知一声,再声明一个委托即可 private NotifyDelegate NotifyStudentPlayMobile; //封装委托,使其符合面向对象 public void add_NotifyStudentLove(NotifyDelegate newdelegate) { NotifyStudentLove += newdelegate; } public void CheckLove() { //某一天AB同学突然发生纠纷!被老师发现啦! string msg = "A同学和B同学早恋啦!!"; //检查是否有人委托老师办事 if (NotifyStudentLove != null) { //果断通知家长 NotifyStudentLove(msg); } } } }
家长类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Delegate { public class Parent { /// <summary> /// 接收消息 /// </summary> /// <param name="msg">通知消息</param> public void ReceiveMsg(string msg) { Console.WriteLine("家长收到通知:" + msg); } } }
运行结果:
家长收到通知:A同学和B同学早恋啦!!
家长收到通知:A同学和B同学早恋啦!!
看完上面的代码场景可知相信对delegate有了更深入的理解,就是“帮我办事”,同时是不是还隐约感觉到了观察者模式?
没错,如果仅仅只用delegate是可以实现观察者模式的,但是,就面向对象而言,对外部对象赋予了delegate太多的方法,又是实例化new,又是+=,-=,甚至你还可以=null。
对于观察者模式来说,观察者不能也不应该有权限实例化事件发布者的消息列表,同时观察者也不应该能控制事件发布者对事件的通知,这些所有的操作应该都是发布者内部的事件而不能交由外部对象来控制。
上面的模拟场景,可以在场景内随意调用teacher.NotifyStudentLove();但是原则上应该是只能在teacher.CheckLove()函数内部才调用的。
有些同学就想了,那我们再封装一次NotifyStudentLove委托不就行了?例如:
private NotifyDelegate NotifyStudentLove; public void add_NotifyStudentLove(NotifyDelegate newdelegate) { NotifyStudentLove += newdelegate; }
没错,我们确实可以这样做~这也是关键字event替我们做的事情。
添加了event关键字之后,.net会通过一系列方法将这个delegate包裹起来从而封装了一部分本来就不应该暴露的行为,就像语法糖{get;set;}一样。
此时,“委托”摇身一变成了“事件”~!但是别以为你穿上马甲我就不认识你了。
下篇将会讲述【温故知新】c#事件event,欢迎拍砖。