网上充斥了太多的伪程序员,除了熟悉键盘的Ctrl+c与Ctrl+v,其余估计连C#都能读错的人却充斥了博客园上面的好多角落,除了知道复制粘贴别人的代码,自己的半点思想没有,一个人犯了错误,竟然能找到几十篇同样错误的文章,可见现在中国所谓技术人员都在干嘛。真希望这些让人蛋疼的人早点离开开发者的家园。本来我是不想写这篇文章的,一则因为以前写的那篇丢了,二则网上的那些伪牛实在让我这个小虾米受不了了。我还是决定写出我们小虾米自己的技术文章。
关于题目,c#就不用多做介绍了,如果对c#这个名词还陌生的话,那么还是先找本基础教程自己一个人捣鼓吧。下面第一点先来讲讲委托。
委托:
Msdn上面关于委托的第一句就是通过将委托于明明方法或者匿名方法关联起来,可以实例化委托。c++中有一个很强大的东东——指针。当然这个也是诟病暴多的地方。C#中是没有指针的,但是很多时候我们需要c++的强大功能,但又不能引进C++中的内存方面的缺陷,于是C#中出现了委托。"delegate 关键字用于声明一个引用类型,该引用类型可用于封装命名方法或匿名方法。委托类似于 C++ 中的函数指针;但是,委托是类型安全和可靠的。"(MSDN)
委托是一种引用费那个方法类型,机制类似于class。一旦我们为委托分配了方法,则委托将与方法具有完全相同的行为。委托方法与其他方法在语法上没有不同点。
委托的特点:
- 类似于c++的指针,但是是类型安全的
- 允许将方法作为参数传递
- 可用于定义回调方法
- 可以链接在一起,对于一个事件可以调用多个委托
- 方法不需要与委托精确匹配,协变(返回子类型,as)与逆变(传入子类型)。
- 匿名方法,允许将代码块作为参数传递
"委托是一种安全地封装方法的类型,它与 C 和 C++ 中的函数指针类似。与 C 中的函数指针不同,委托是面向对象的、类型安全的和保险的。委托的类型由委托的名称定义。"此句话中"委托是面向对象的"需要额外注意。委托的本质就是一个类,所以实例化的一个委托就是一个对象,具有对象的一切特征。
委托基本概念就是指针、面向对象。理解这两点,委托也就基本掌握了。
"委托类型派生自 .NET Framework 中的 Delegate 类。委托类型是密封的,不能从 Delegate 中派生委托类型,也不可能从中派生自定义类。由于实例化委托是一个对象,所以可以将其作为参数进行传递,也可以将其赋值给属性。这样,方法便可以将一个委托作为参数来接受,并且以后可以调用该委托。这称为异步回调,是在较长的进程完成后用来通知调用方的常用方法。以这种方式使用委托时,使用委托的代码无需了解有关所用方法的实现方面的任何信息。此功能类似于接口所提供的封装。"
如果理解了上面这句话中的玄机,或者你能问出"那么何时使用委托何时使用接口"那么你已经真的理解了委托,而且你也深刻理解了接口(也就是说你不会再纠缠于抽象类与接口的区别问题,这个问题是很多新手最无法搞清的问题)。下面就简要转述一下msdn上关于该问题的解答:
委托(封装单方法,多实现)【设计架构模式决定】 | 接口(封装一组相关方法,单实现)【设计架构模式决定】 |
当使用事件设计模式时。 当封装静态方法可取时。 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。 需要方便的组合。 当类可能需要该方法的多个实现时。 | 当存在一组可能被调用的相关方法时。 当类只需要方法的单个实现时。 当使用接口的类想要将该接口强制转换为其他接口或类类型时。 当正在实现的方法链接到类的类型或标识时:例如比较方法。(特例,需要理解) |
至于委托的使用步骤,我就不写出来了。如果真的不会,那么自己去看msdn吧。
但是需要补充的是泛型委托,这个在实际中经常用到。
public delegate string ProcessDelegate<T, S>(T s1, S s2, params S[] s3); public string SS(string ss, int ii, int[] iii){ return null; } public static void Main(){ AsyncDemo sc = new AsyncDemo(); ProcessDelegate<string, int> pd = new ProcessDelegate<string, int>(sc.SS); } |
事件:
"在发生其他类或对象关注的事情时,类或对象可以通过事件通知他们。发送(或引发)事件的类称为"发行者",接受(或处理)事件的类称为"订户"。"
上面这句话描述了事件的最本质功能,用于底层通知上层。正常的架构设计都是分层结构,而分层结构有一点很重要的就是底层对于上层的无知,当初这样设计是为了解耦,为了更好的面向对象,但是带来的问题是如何解决自下而上的信息流。因为自上而下的调用,我们通过接口就可以搞定一切了,上层可以看到下层提供的服务接口,那么正常的调用可以保证一路向下,底层调用中层提供的服务接口,中层的服务接口的实现中调用了底层的服务接口,这样感觉很是完美的设计模式。每一层都不再依赖彼此,隐藏了实现细节。但是现在遇到一个最简单的问题:如果需要底层来触发上层的行为,如何实现。很多程序员告诉我这个简单,轮询啊,底层不断轮询这一个事情的发生状况,如果发生了则启动一个线程专门去处理这个事情。这种解决方案只需要在底层多开出一个服务接口,该服务接口就是表示目前发生了什么事情,然后上层定时查看该接口,如果发生则采取相应操作。当然该种解决方案也是一种解决途径,但是估计你也觉得不好,第一无法实时,因为轮询,那么必定存在一个时差问题,也就是常说的响应时间问题。还有就是单独的轮询线程需要空间与时间的消耗。最让人郁闷还在于这个对于时空的消耗竟然与响应时间是反相关的,总之你想响应时间短,那么就意味着你不得不浪费大量时空,反之亦然。当然此种方法还要解决多线程冲突的问题,涉及到多线程冲突,锁解锁的问题,那么我觉得就不怕你的逻辑能力有多强,耐心有多大,随着项目规模的变大,线程的变多,你大脑崩溃那是早晚的事情。
此处我们引入事件模式。
先来看看事件的特征:
- 发行者确定何时引发事件,订户确定执行何种操作来响应该事件
- 一个事件可以有多个订户。一个订户可处理来自多个发行者的多个事件
- 没有订户的事件永远不会被调用
- 事件通常用于通知用户操作
- 如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序
- 支持异步调用
- 可以利用事件同步线程
- 在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的
C#类库中自带了一大堆事件,尤其那些控件。而对于我说到的这个底层触发上层的问题,那么绝大多数是需要自定义事件的。(库中自带事件的使用我就不讲了,如果这个你不会的话,未免对不起观众了。)所以下面就开始着重讲讲自定义事件的问题:
事件是类和对象向外界发出的消息,事件的执行是通过事件委托的方式,调用我们所准备好的处理方法。要响应某些事件并针对某些事件执行我们指定的方法,需要做到以下几步:
- 声明委托、事件
- 添加事件的触发方法,也就是通知接受者方法
- 添加事件引发方法
- 接受者处本地化响应方法
- 接受者订阅事件
//发布事件的类 public class TestEventSource{ //定义事件参数类 public class TestEventArgs : EventArgs{ public readonly char KeyToRaiseEvent; public TestEventArgs(char keyToRaiseEvent){ KeyToRaiseEvent = keyToRaiseEvent; } } //定义delegate public delegate void TestEventHandler(object sender, TestEventArgs e); //用event 关键字声明事件对象 public event TestEventHandler TestEvent; //事件触发方法 protected virtual void OnTestEvent(TestEventArgs e){ if (TestEvent != null) TestEvent(this, e); } //引发事件 public void RaiseEvent(char keyToRaiseEvent){ TestEventArgs e = new TestEventArgs(keyToRaiseEvent); OnTestEvent(e); } } //监听事件的类 public class TestEventListener{ //定义本地处理事件的方法,他与声明事件的delegate具有相同的参数和返回值类型 public void KeyPressed(object sender, TestEventSource.TestEventArgs e){ Console.WriteLine("发送者:{0},所按得健为:{1}", sender, e.KeyToRaiseEvent); } //订阅事件 public void Subscribe(TestEventSource evenSource){ evenSource.TestEvent += new TestEventSource.TestEventHandler(KeyPressed); } //取消订阅事件 public void UnSubscribe(TestEventSource evenSource){ evenSource.TestEvent -= new TestEventSource.TestEventHandler(KeyPressed); } } //测试类 public class Test{ public static void Main(){ //创建事件源对象 TestEventSource es = new TestEventSource(); //创建监听对象 TestEventListener el = new TestEventListener(); //订阅事件 Console.WriteLine("订阅事件\n"); el.Subscribe(es); //引发事件 Console.WriteLine("输入一个字符,再按enter键"); string s = Console.ReadLine(); es.RaiseEvent(s.ToCharArray()[0]); //取消订阅事件 Console.WriteLine("\n取消订阅事件\n"); el.UnSubscribe(es); //引发事件 Console.WriteLine("输入一个字符,再按enter健"); s = Console.ReadLine(); es.RaiseEvent(s.ToCharArray()[0]); } } |
——代码转自网上
注意理解其中每一个成员变量的存在意义。引发事件是真正将事件与变化绑定的地方,引发事件决定了什么状况会触发这个事件。而触发方法则是负责在接收到引发事件的命令的时候开始通知事件接受者。事件接受者接到通知之后则开始负责执行本地响应方法。其中委托的原型必须定义为public。上例中的事件触发方法前面的修饰符protected virtual则是保证在控制访问修饰符的前提下保证可继承并支持重写事件触发方法。