第九讲 委派与事件
委派
委派是C#引入的一种新型的数据类型,它非常类似于C/C++中的函数指针,它常常用于在编译时未绑定的动态方法调用。但与函数指针所不同的是委派在C#完全地实现了面向对象,它既可以引用静态方法,也可以引用实例方法,而函数指针只能引用静态方法。C#中的委派同时也是类型安全。
作为一种面向对象的数据类型,委派的使用分为三步:委派声明,委派实例化和委派调用。委派声明即定义一个封装特定参数类型和返回值类型的方法体(静态方法或实例方法)的数据类型,看下面的示例:
delegate int Compute (int left,int right);
可以看到,委派类型Say包含了方法的两个要素——参数类型和返回值类型。委派类型和方法只有满足下面两个条件,我们才说他们是兼容的:
1. 参数的数量相同,并且他们的类型也按序相同
2. 返回值相同
委派类型相同指的是他们声明的类型为同一类型(名字一样)。而委派实例的相等则是指他们绑定的方法为同一个方法或相同的方法按相同的顺序组成的方法链,而他们本身的类型只要兼容即可(同样满足上面两个条件),不必强求同名。
委派实例化即将委派类型绑定特定的方法的过程,和其他对象的实例化相似,都需要用new语句,只是必须接受和该委派类型兼容的方法名作为new语句的参数。如果是实例方法,必须采用两者中间加点号的方式同时提供实例对象和方法。
委派实例化后就可以象调用方法一样进行委派调用了。下面是一个完整的例子,比较典型地展示了我们通常使用委派的三个步骤:
using System;
delegate int Compute(int left,int right);//委派类型声明
class A
{
public int Mul(int num1,int num2)
{
return num1*num2;
}
}
class Test
{
public static int Add(int num1,int num2)
{
return num1+num2;
}
static void Main()
{
Compute c1=new Compute(Add);//静态方法的实例化
A a=new A();
Compute c2=new Compute(a.Mul);//实例方法的实例化
int r1=c1(2,3);//委派调用
int r2=c2(2,3);//委派调用
Console.WriteLine(r1);
Console.WriteLine(r2);
}
}
委派组合
委派组合是指一个委派类型可以同时绑定多个可调用的方法。由于绑定多个方法,C#规定组合委派的返回类型必须为void。当然这多个方法的参数类型也必须都和组合委派的参数类型兼容。委派组合采用“+”或“+=”来将两个委派类型合并为一个新的组合委派类型。“-”或“-=”用来从组合委派类型上移除已经绑定绑定一个方法的委派实例或绑定多个方法的组合委派实例。
需要注意的是在做委派的组合和移除操作的时候,参与操作的委派类型必须相同——注意是“相同”不是“相等”。看下面的例子:
using System;
delegate void MyDelegate(string s);
class Test
{
public static void Hello(string s)
{
Console.WriteLine("Hello, {0}!", s);
}
public static void Goodbye(string s)
{
Console.WriteLine("Goodbye, {0}!", s);
}
public static void Main()
{
MyDelegate a, b, c, d;
a = new MyDelegate(Hello);
b = new MyDelegate(Goodbye);
c = a + b;//委派的组合
d = c - a;//委派的移除
a("A");
b("B");
c("C");
d("D");
}
}
程序输出:
Hello, A!
Goodbye, B!
Hello, C!
Goodbye, C!
Goodbye, D!
我们可以看到在委派组合后,组合委派类型c将同时绑定了两个方法Hello(string s)和Goodbye(string s)。从它的输出我们可以看到这一点。
需要注意的是组合委派中的方法调用是有顺序的,如果我们在上面的例子中修改c = a + b;为c=b + a;我们将看到输出序列的改变。
同样委派的移除也是有顺序的,它总是从绑定的委派实例中的最后开始搜索需要移出的委派实例——注意这里的移除是以委派实例为单位的,而不是以方法为单位的。读者可以将上面的例子改成下面这样看看结果:
public static void Main()
{
MyDelegate a, b, c, d;
a = new MyDelegate(Hello);
b = new MyDelegate(Goodbye);
c = a+b+a;//委派的组合
b+=b;//委派的组合
d = c - b;//委派的移除
a("A");
b("B");
c("C");
d("D");
}
事件
事件是C#中类的一种成员,它和前面讲过的域有着一样的修饰符和相应的语义。简单的讲,事件就是一种消息通知,它是对象之间传递消息的一种方式。C#采用一种称作“发布——登记——接受”的逻辑来在对象之间传递消息,通知某个事件的发生,如鼠标对某个按钮的点击,电子邮件的到来等等。一个对象只有在发布某事件后才有权力在该事件到来时通知其它对象,而一个对象只有在登记某事件后才有资格在该事件发生后接受发布事件的对象的通知。注意这里发生的事件是在事件发布者对象里发生的,而这一事件又有必要让另一对象知道,这才有这样的事件模型的存在的基础和必要。
在事件模型中,事件发送者起初并不知道哪个对象来接受这样的事件。但又要通知到它,怎么办呢?这就需要用到我们前面的委派了。下面是通常用于事件的一个委派例子:
public delegate void SomeEventHandler(object sender, SomeEventArgs e);
有了委派我们才可以做事件声明:
public event SomeEventHandler SomeEvent;//事件声明
一个接受事件的对象必须在它的类声明中提供处理相应事件的方法:
public class SomeReceiverClass
{
。。。//类的其他实现
public void SomeEventProcessMethod(object sender,SomeEventArgs e)
{
。。。//处理事件的方法。
}
}
而一个事件发送者的对象必须在它的类声明中提供事件声明,和相应的事件触发机制:
public class SomeSenderClass
{
。。。//类的其他实现
public event SomeEventHandler SomeEvent;//事件声明
protected virtual void OnSomeEvent(SomeEventArgs e)
{
if(SomeEvent !=null)
SomeEvent(this,e);
}
public void SomeTiggerMethod()
{
。。。//其它实现。
SomeEventArgs e=new SomeEventArgs(。。。);//实例化事件参数
OnSomeEvent(e);//触发事件。
}
}
现在事件的发布,处理方法,触发方法,发送者与接受者之间的联系——委派都建好了,只剩下我们在最后编程时登记的工作了:
public class SomeApp
{
public static Main(string[] args)
{
SomeEventReceiver myReceiver=new SomeEventReceiver();//实例化事件接受者
SomeEventReceiver mySender=new mySender();//实例化事件发送者
MySender.SomeEvent+=new
SomeEventHandler(myReceiver.SomeEventProcessMethod);//登记事件
。。。//其他一些有可能引发SomeEvent的事件的操作。
}
}
注意这里用“+=”和“-=”的符号来登记或取消登记操作。为实现多播事件,我们可以多次登记便可:
SomeEventReceiver mySender=new mySender();//实例化事件发送者
SomeEventReceiver1 myReceiver1=new SomeEventReceiver1();//实例化事件接受者
SomeEventReceiver2 myReceiver2=new SomeEventReceiver2();//实例化事件接受者 mySender.NewMailEvent+=new
NewMailEventHandler(myReceiver1. SomeEventProcessMethod );
mySender.NewMailEvent+=new
NewMailEventHandler(myReceiver2. SomeEventProcessMethod);
末了,我们给出一个完整的邮件通知程序,并加油详细注释,大家可以在这个基础上更深一步地研究C#事件模型及其底层机制。
using System;
//信邮件事件参数类
public class NewEmailEventArgs: EventArgs
{
public NewEmailEventArgs(string subject, string message)
{
this.subject = subject;
this.message = message;
}
public string Subject
{
get
{
return(subject);
}
}
public string Message
{
get
{
return(message);
}
}
string subject;
string message;
}
//新邮件委派声明
public delegate void NewMailEventHandler(object sender,NewEmailEventArgs e);
//邮件发送类声明
public class EmailSender
{
//新邮件事件声明
public event NewMailEventHandler NewMailEvent;
protected void OnNewMail(NewEmailEventArgs e)
{
if (NewMailEvent != null)
NewMailEvent(this, e);
}
//发送邮件
public void SendMail(string subject, string message)
{
NewEmailEventArgs e = new NewEmailEventArgs(subject, message);
OnNewMail(e);
}
}
//邮件接收者声明
public class EmailReceiver
{
public EmailReceiver(string name)
{
_name=name;
}
//处理收到新邮件事件的方法
public void ComeMail(object sender, NewEmailEventArgs e)
{
Console.WriteLine("This is Receiver {0} ,receive a new email:/n{1} {2}",_name,
e.Subject, e.Message);
}
string _name;
}
//测试类
public class Test
{
public static void Main()
{
EmailSender mySender = new EmailSender();//邮件发送者
EmailReceiver myReceiver1 = new EmailReceiver("Receiver1");//第一个接受者
EmailReceiver myReceiver2 = new EmailReceiver("Receiver2");//第二个接收者
mySender.NewMailEvent+=new
NewMailEventHandler(myReceiver1.ComeMail);//登记邮件事件
mySender.NewMailEvent+=new
NewMailEventHandler(myReceiver2.ComeMail); //登记邮件事件
mySender.SendMail("Hello!", "I am from Sender!!!");//发送邮件,多播
mySender.NewMailEvent-=new
NewMailEventHandler(myReceiver2.ComeMail); //取消登记邮件事件
mySender.SendMail("Hello!", "I am from Sender!!!");//再次发送邮件,单播
}
}
如大家所见,我们可以得到这样的输出结果:
This is Receiver Receiver1 ,receive a new email:
Hello! I am from Sender!!!
This is Receiver Receiver2 ,receive a new email:
Hello! I am from Sender!!!
This is Receiver Receiver1 ,receive a new email:
Hello! I am from Sender!!!
实际上,在以后遇到的不管是Windows Forms编程,还是ASP.Net编程,以及众多的异步操作编程,我们都会大量的接触到事件编程的模型,在这些编程中,有时系统为我们做好了发送类,接受类,委派的声明实现,只要我们登记和取消登记即可达到操纵事件的目的。而有些,尤其是涉及到系统底层的开发,往往要我们自己实现整个事件的发送,接受,委派等等诸多底层机制。