委托与事件
首先给出一堆委托的介绍内容,不理解的话,也没有问题,等看了 Demo 再回过头来看就 OK 了:
委托是一个类,它定义了方法的类型,使得可以将方法当做另一个方法的参数来进行传递,
这种将方法动态地赋值给参数的做法,
可以避免在程序中大量的使用判断语句,同时使得程序具有更好的扩展性。
委托是一个可以保存对方法的引用的类,委托类具有一个签名,
并且它只能对与其签名匹配的方法进行引用,
这样,委托就等效于一个类型安全函数指针或者是一个回调。
一个委托声明足以定义一个委托类,声明提供委托的签名,公共语言运行库提供实现。
委托呢,可以看做是对函数的一个封装,可以当做给方法的特征指定一个名称,
委托具有如下特点:委托类似于 C++ 中的函数指针,但其实类型安全的,
委托允许将方法作为参数进行传递,
委托可以用于定义回调函数,
委托可以链接在一起,例如,可以对一个事件调用多个方法,
方法名不必与委托签名完全匹配。
下面的 Demo 将演示如何将委托看做一个类型,并且通过委托来实现将方法作为参数传递
Delegate.DelegateType 类的代码如下
using System;
namespace Delegate
{
/// 首先必须要定义一个委托类型,在定义委托类型时
/// 您可以有参数,同时你可以让这个委托返回一个值,比如 int 类型的值
/// </summary>
public delegate void DelegateDemo();
public class DelegateType
{
private string name;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name"></param>
public DelegateType(string name)
{
this.name = name;
}
/// <summary>
/// 此处定义一个方法,用来实现将这个方法传递给委托
/// 然后通过委托类型来调用这个方法
/// </summary>
public void SayHello()
{
Console.WriteLine("Hello , " + name);
}
public void SayBye()
{
Console.WriteLine("Good Bye , " + name);
}
/// <summary>
/// 这里就是成功的使用委托了
/// 可以看到是以一个委托类型来作为参数传递
/// 在这里,您完全可以把这个委托类型的参数 delegatePara 看做事一个方法
/// 这样就是成功实现利用委托来传递方法参数了
/// </summary>
/// <param name="delegatePara"></param>
public void Talk(DelegateDemo delegatePara)
{
delegatePara();
}
}
}
然后就是 Main 方法中的代码如下
using System;
namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
Delegate.DelegateType dele =
new Delegate.DelegateType("XiaoZhen");
//通过委托传递进方法 Talk 的参数是一个方法类型 SayHello
dele.Talk(dele.SayHello);
dele.Talk(dele.SayBye);
Console.ReadKey();
}
}
}
下面就给出效果的截图了
从上面的 Demo 可以看出,通过委托,
我们已经成功实现了将一个方法作为另外一个方法的参数来进行传递。
上面的代码中呢,我们是通过将在一个委托上绑定一个方法来实现的,
而实质上,一个委托上是可以绑定 n 个方法的,
下面我把上面的 Demo 中的 Main 方法中稍微改动一下来实现一个委托上绑定 n 个方法:
using System;
namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
Delegate.DelegateType dele =
new Delegate.DelegateType("XiaoZhen");
Delegate.DelegateDemo demo;
//在一个委托上绑定了两个方法,
//同时,将委托作为参数传递给方法时便传递了两个方法给参数方法
demo = dele.SayBye;
demo += dele.SayHello;
dele.Talk(demo);
Console.ReadKey();
}
}
}
最后的结果呢,和上面的结果是一模一样的。
从上面的这个 Demo 呢就可以看出在一个委托上可以绑定 n 个方法,
然后将这个委托变量传递给方法便实现了一次性传递多个方法给指定的方法。
上面的代码其实还可以改动一下的,
前面曾提到过委托实质上也是一个类而已,既然是类,那么当然就可以实例化,
不过,对委托的实例化,您必须提供一个一个引用参数,来作为其构造函数的参数,
还要注意一点就是,
提供的这个引用参数(也就是一个方法)必须和委托具有相同的签名(也就是参数和返回值),
下面就来将上面的 Demo 中的 Main 方法再改动一下以验证上面提到的实例化一个委托:
using System;
namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
Delegate.DelegateType dele =
new Delegate.DelegateType("XiaoZhen");
Delegate.DelegateDemo demo =
new Delegate.DelegateDemo(dele.SayBye);
//由于在实例化委托时,已经提供了 SayHello ,所以下面就只绑定 SayHello 了
demo += dele.SayHello;
//其会按照方法在委托上的注册顺序来先后执行绑定在委托类型变量上的方法
//上面的注册顺序为先是 SayBye 然后是 SayHello
//所以执行的顺序会是先执行 SayBye 然后才是 SayHello
demo();
Console.ReadKey();
}
}
}
上面改动后的效果和前面的效果是一模一样的,从这里可以看出,确实是可以实例化一个委托类型实例的,
然后,如果您将上面的实例化委托的构造函数中的方法的引用去掉,那么便编译时会出错误,
也就是说在实例化一个委托时必须提供一个方法的引用。
下面再将上面的 Demo 中的 Main 方法中的一部分改动一下,来实现直接使用委托类型调用方法:
using System;
namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
Delegate.DelegateType dele =
new Delegate.DelegateType("XiaoZhen");
Delegate.DelegateDemo demo;
demo = dele.SayBye;
demo += dele.SayHello;
//其会按照方法在委托上的注册顺序来先后执行绑定在委托类型变量上的方法
//上面的注册顺序为先是 SayBye 然后是 SayHello
//所以执行的顺序会是先执行 SayBye 然后才是 SayHello
demo();
Console.ReadKey();
}
}
}
最后的执行效果呢,和上面的效果是一模一样的。
下面就来总结一下上面的整个 Demo
首先呢,是可以得出,委托其实也是一个类,可以声明一个委托类型(也就是一个类类型),
然后呢,需要注意的是,由于委托就是一个类,所以在任何可以声明类的地方都是可以声明委托的,
而后就是,可以通过委托来实现将一个方法作为参数来传递给另外一个方法,
同时,一个委托上是可以绑定 n 个方法的,
并且这些绑定上去的方法的执行顺序是按注册到委托上的顺序先后执行的。
下面再给出一堆事件的介绍内容,看不懂的话,等看完 Demo 后再来看就 OK 了:
一般来说,事件和委托是相互配合使用的
事件是对象发送的消息,以发信号通知操作的发生,
操作可能是由用户交互引起,也可能由某些其他的程序逻辑触发,
引发事件的对象称为事件发送方,捕获事件并对其作出响应的对象叫做事件接收方,
在事件通信机制中,事件的发送方类是不知道哪个对象或者方法将会接收到由它引发的事件,
所以,需要一个在发送方和接收方之间定义一个媒介,这个媒介用来实现在事件发送方和事件接收方进行沟通,
而这个媒介就是委托,委托实质上也只是一个特殊的类型,这个类型提供了函数指针的功能。
就比如,在大学里的一名辅导员要想管住所有的学生,不准学生 11 点以后上网,这几乎是不可能的,
此时,可以通过班长或者是学习委员来进行监督,当有同学 11 点后没有睡觉的,
就通过班长或者是学习委员给辅导员报告,在这个关系里面,班长或者是学习委员充当的就是委托,
而事件发送方就是 11 点后还在上网的同学,事件接收方就是辅导员了(作出处罚)。
声明一个事件不过类似于声明一个进行了封装的委托类型的变量而已。
在 .Net Framework 中,对事件和委托的编码规范如下:
委托类型的名称都应该以 EventHandler 结束;
委托的原型定义:有一个 void 返回值,
并接收两个参数,一个是 object 类型,一个是 EventArgs 类型;
事件的命名为委托名去掉后面的 EventHandler ,
继承自 EventArgs 的类型应该以 EventArgs 结尾。
private void btn_Click(object sender, EventArgs e){}
上面这个就是一个典型的 Click事件了,其中包括 object 类型的 sender 参数,
和一个 EventArgs 类型的 e 参数,其中 sender 参数代表的是触发这个事件的对象,
在一般是 ID 为 btn 的一个对象触发(一般为一个按钮)的 Click 事件,而其中的 e 呢,
则代表了传递进来的所需要的一些信息。
下面来看一个在《大话设计模式》上关于事件的一个 Demo
其完成的是当猫来了的时候,便会自动通知老鼠,然后,老鼠就会跑掉的功能;
先来看 Cat 类吧
using System;
namespace Event
{
public class Cat
{
private string name;
public Cat(string name)
{
this.name = name;
}
/// <summary>
/// 定义一个委托,注意编程规范,以 EventHandler 结尾
/// 同时在在其中定义了参数类型 CatEatMouseEventArgs
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void CatEatMouseEventHandler(
object sender, CatEatMouseEventArgs e);
//定义一个事件,
//其事件类型是上面定义的委托 CatEatMouseEventHandler
public event CatEatMouseEventHandler CatEatMouse;
public void CatCome()
{
Console.WriteLine("哈哈哈,我是 {0} ,我要吃了你们;", name);
//当猫说要吃老鼠时,如果上面定义的事件对象上绑定了事件时
//便执行这些事件
if (CatEatMouse != null)
{
//设置引发该事件的对象为当前该类的实例
//同时设置参数 CatEatMouseEventArgs
//让这个参数携带当前猫名字作为事件参数
CatEatMouse(this, new CatEatMouseEventArgs(this.name));
}
}
}
}
下面再来看 Mouse 类
using System;
namespace Event
{
public class Mouse
{
private string name;
public Mouse(string name)
{
this.name = name;
}
/// <summary>
/// 由于这个 Run 方法必须要绑定到事件 CatEatMouse 上去
/// 而事件 CatEatMouse 的类型又是一个委托类型 CatEatMouseEventHandler
/// 而在这个委托类型中 CatEatMouseEventHandler 中定义了两个参数
/// 由前面的委托部分的介绍可以知道,
/// Run 方法的签名必须和 CatEatMouseEventHandler 这个委托类型一致
/// 才能够绑定成功
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void Run(object sender, CatEatMouseEventArgs e)
{
Console.WriteLine("{0} 要来了,{1} 快跑啊;", e.Name, this.name);
}
}
}
还有一个就是参数 CatEatMouseEventArgs 类
using System;
namespace Event
{
/// <summary>
/// 定义一个类,使其继承自 EventArgs(这个类是包含事件数据的类的基类)
/// </summary>
public class CatEatMouseEventArgs:EventArgs
{
//将 name 设置为只读属性,也就是确保只有在实例化这个参数时,
//即构造函数中唯一设置该值
private string name;
public string Name
{
get { return name; }
}
public CatEatMouseEventArgs(string name)
{
this.name = name;
}
}
}
最后就来看 Main 函数了
using System;
using Event;
namespace EventTest
{
class Program
{
static void Main(string[] args)
{
//首先要实例化一个猫
Cat cat = new Cat("黑猫警长");
//然后实例化 5 只老鼠
Mouse mouseOne = new Mouse("老鼠一号");
Mouse mouseTwo = new Mouse("老鼠二号");
Mouse mouseThree = new Mouse("老鼠三号");
Mouse mouseFour = new Mouse("老鼠四号");
Mouse mouseFive = new Mouse("老鼠五号");
//将老鼠一号,二号,三号的 Run 方法
//绑定到猫类中定义的 CatEatMouse 事件上
cat.CatEatMouse += mouseOne.Run;
cat.CatEatMouse += mouseTwo.Run;
cat.CatEatMouse += mouseThree.Run;
cat.CatCome();
Console.ReadKey();
}
}
}
再来看看效果吧
从上面的效果可以看出,由于老鼠四号和五号没有绑定到事件上,所以没有得到通知,
而一号,二号,三号则得到了通知。
通过上面的这个 Demo 再来看前面关于事件的那一堆介绍吧,
你看,如果没有委托的话,当猫说要吃老鼠了的时候,
老鼠如何得到这个消息,然后逃跑呢?
但有了委托以后,再让老鼠逃跑方法的签名和委托签名一致,
并且将事件的类型声明为委托类型,同时,将老鼠逃跑方法绑定到猫要吃老鼠了这个事件上,
(这里由于老鼠逃跑的签名和委托一致,而猫吃老鼠这个事件又是委托类型,所以它们可以实现绑定)
此时,便通过委托将猫来了和老鼠要逃跑联系在了一起,
从这里边可以看出,委托实质上起到的是一个中介的作用。
上面有一个需要注意的,就是在 CatEatMouseEventArgs 类中,其继承的 EventArgs 类,
这个 EventArgs 类呢,是包含事件数据的基类,在 WinForm 中经常会使用,比如:
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
这个就是在 GridView 中的 RowUpdating 事件了,
其中的 GridViewUpdateEventArgs 参数类型就是继承自 EventArgs 基类
还有必须要提一下的就是,在设计模式的观察者模式中,在 .Net 中便是通过委托和事件机制来实现的,
关于观察者模式的内容,将会在后续的博文中做个介绍的~~~