委托:
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#类库中自带了一大堆事件,尤其那些控件。而对于我说到的这个底层触发上层的问题,那么绝大多数是需要自定义事件的。(库中自带事件的使用我就不讲了,如果这个你不会的话,未免对不起观众了。)所以下面就开始着重讲讲自定义事件的问题:
事件是类和对象向外界发出的消息,事件的执行是通过事件委托的方式,调用我们所准备好的处理方法。要响应某些事件并针对某些事件执行我们指定的方法,需要做到以下几步:
- 声明委托、事件
- 添加事件的触发方法,也就是通知接受者方法
- 添加事件引发方法
- 接受者处本地化响应方法
- 接受者订阅事件
在windows 编程中用到最多的就是控件的时间了,微软给我们很好的方式,把注意力放到事件执行方法的设计和编码上,但是但我们真正弄懂了事件的真正出发执行原理的话,对我们的编程的提高真是非常榜的,例如在windows编程中 如果我单击了一个button按钮触发了button 的click事件 Button1_Click(){} , 但是有时候我们编程的时候,不但想要触发button 的单击事件,我还想要把其他的时间也要调用下来顺序执行,要实现这种方式,除了在方法最后对其他方法的调用,还可以利用将其他需要顺序执行的方法封装到button的click 事件的委托对象中,这样就能够顺序执行毁掉方法列表中的程序了,而这种方式的实现是以清楚事件触发和委托的调用为前提的。
事件是类和对象向外界发出的消息,事件的执行是通过事件委托的方式,调用我们所准备好的处理方法,而是先消息的响应的。要响应某些事件并针对某些事件执行我们意定的方法,需要做到以下几步:
1、声明事件委托。
2、声明事件。
3、添加事件的触发方法。
4、添加事件的处理程序(响应事件的方法)。
5、将指定的事件处理程序邦定到要处理的事件上(订阅事件)。
6、用户信息操作,并触发事件(调用事件的触发方法)。
7、通过事件委托的回调,执行我们需要的事件处理程序。
下面我们举一个简单的自定义事件处理程序的例子(控制台程序)
namespace 事件
{
//发布事件的类
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]);
}
}
}
程序执行结
订阅事件
输入一个字符,再按enter键
aaaa
发送者:事件.TestEventSource,所按得健为:a
取消订阅事件
输入一个字符,再按enter健
TestEventSource类。他就相当于windows控件类一样,是事件的源,里面包含有事件的声明,以及存储调用参数的事件参数类,以及事件的触发方法。
TestEventListener类。他提供了事件处理程序,并实现了事件处理程序和事件对象的邦定,当然时间处理程序可以放在别处, 跟邦定程序(订阅事件)放在一起便于理解和调用
Test 类,实例化自定义事件的事件源对象,并调用 TestEventListener类中的Subscribe(es);方法进行事件对象和事件处理程序的邦定(订阅事件),调用 TestEventSource类中的RaiseEvent(char keyToRaiseEvent)引发对象,并有对象所指定的委托回调处理事件。完成整个自定义事件。
其中 RaiseEvent(char keyToRaiseEvent) 就相当于main()一样是自定义事件的执行入口, 从这个法开始---〉调用事件委托----〉查找订阅事件程序找到事件所封装的方法集----〉由委托回调事件处理程序并传递参数---〉执行事件处理程序。