在前面一篇中写到了委托,也说了委托是C#中很多特性的基础,这篇要讲的事件,就是建立在委托之上的。在C#1.0中,委托和事件是最重要的两个特性。
1、什么是事件?
事件设计到两类角色——事件发布者和事件订阅者。当某个事件发生后,事件发布者会发布消息;事件订阅者会接收到信息,并做出相应的处理,这就是事件的过程。
2、使用事件
2.1 定义事件
在C#中定义事件和定义类的成员是很相似的,只要一个event关键字就可以了。比如:
public event EventHandler birthday;
其中event是关键字,而EventHandler是委托类型。
所以可以把事件定义的结构总结为:访问修饰符 event 委托类型 事件名;其中委托类型可以是自定义的委托类型,也可以是.NET类库中预定义的委托类型EventHandler。
2.2 订阅和取消事件
事件订阅者需要订阅事件发布者发布的事件消息,以便在事件被触发式接收消息并做出相应处理。在C#中,可以使用“+=”来订阅事件,使用“-=”来取消订阅事件。
public class Bridegroom
{
//自定义委托
public delegate void MarryHandler(string msg);
//使用自定义委托类型定义事件,事件名为MarryEvent
public event MarryHandler MarryEvent;
//发出事件
public void OnMarriageComing(string msg)
{
//判断是否绑定了事件处理方法
if(MarryEvent!=null)
{
//触发事件
MarryEvent(msg);
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//实例化朋友对象
Friend friend1=new Friend("张三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”来订阅事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//发出通知,此时只有订阅了事件的对象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="来取消事件订阅,此时李四将收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件处理函数,该函数需要符合MarryHandler委托的定义
public void SendMessage(string message)
{
Console.WriteLine(message);
Console.WriteLine(this.Name+"收到了,到时候准时参加");
}
}
值得注意的是,事件处理函数的定义需要与自定义的委托定义保持一致,即参数个数,参数类型和返回类型等需要与委托相同。
除了使用自定义委托类型来定义事件外,还可以使用.NET类库中预定义的委托类型EventHandler来定义事件,需要注意它们的参数。
public class Bridegroom
{
//使用.NET类库中的类型定义事件,事件名为MarryEvent
public event EventHandler MarryEvent;
//发出事件
public void OnMarriageComing(string msg)
{
//判断是否绑定了事件处理方法
if(MarryEvent!=null)
{
Console.WriteLine(msg);
//触发事件
MarryEvent(this,new EventArgs());
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//实例化朋友对象
Friend friend1=new Friend("张三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”来订阅事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//发出通知,此时只有订阅了事件的对象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="来取消事件订阅,此时李四将收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件处理函数,该函数需要符合MarryHandler委托的定义
public void SendMessage(object s,EventArgs e)
{
Console.WriteLine(this.Name+"收到了,到时候准时参加");
}
}
EventHandler是.NET类库中预定义的委托类型,用于处理不包含事件数据的事件。使用Reflector来查看EventHandler的具体定义:
[Serializable, ComVisible(true), __DynamicallyInvokable]
public delegate void EventHandler(object sender, EventArgs e);
|
从定义中可以看出,该委托类型的返回类型为void,第一个参数sender负责保存触发事件对象的引用,其类型为object;第二个参数e负责保存事件数据。EventArgs类也是.NET类库中定义的类,它不保存任何数据,如果想在事件中包含事件数据,就必须使用EventArgs的派生类来实现。
2.3 扩展EventArgs类
上面说了,如果要在事件中包含事件数据,就必须使用EventArgs的派生类。具体的实现代码如下:
public class MarryEventArgs:EventArgs
{
public string Message;
public MarryEventArgs(string msg)
{
Message=msg;
}
}
public class Bridegroom
{
//自定义委托类型,委托包含两个参数
public delegate void MarryHandler(object sender,MarryEventArgs e);
//使用自定义委托类型定义事件,事件名为MarryEvent
public event MarryHandler MarryEvent;
//发出事件
public void OnMarriageComing(string msg)
{
//判断是否绑定了事件处理方法
if(MarryEvent!=null)
{
//触发事件
MarryEvent(this,new MarryEventArgs(msg));
}
}
static void Main(string[] msg)
{
Bridegroom bridegroom=new Bridegroom();
//实例化朋友对象
Friend friend1=new Friend("张三");
Friend friend2=new Friend("李四");
Friend friend3=new Friend("王五");
//使用“+=”来订阅事件
bridegroom.MarryEvent+=new MarryHandler(friend1.SendMessage);
bridgeroom.MarryEvent+=new MarryHandler(friend2.SendMessage);
//发出通知,此时只有订阅了事件的对象才能收到通知
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.WriteLine("------------------------------------");
//使用"-="来取消事件订阅,此时李四将收不到通知
bridegroom.MarryEvent-=new MarryHandler(friend2.SendMessage);
bridegroom.MarryEvent+=new MarryHandler(friend3.SendMessage);
bridegroom.OnMarriageComing("Friend,I Will Marry!!");
Console.ReadKey();
}
}
public class Friend
{
public string Name;
public Friend(string name)
{
Name=name;
}
//事件处理函数,该函数需要符合MarryHandler委托的定义
public void SendMessage(object s,MarryEventArgs e)
{
Console.WriteLine(e.Message);
Console.WriteLine(this.Name+"收到了,到时候准时参加");
}
}
通过自定义MarryEventArgs事件类扩展了EventArgs类,此时MarryEventArgs带有一个名为Message的事件参数;然后在订阅对象的SendMessage方法中,通过e.Message的方式获得了事件数据,并把事件数据输出。
3、事件的本质
从以上的例子我们可以知道,事件是在委托的基础之上的。那么,它们到底有着什么样的关系呢,这个就必须通过Reflector来窥探了。
简单的源代码:
namespace 窥探事件本质
{
class Program
{
public delegate void MarryHanler(string msg);
public event MarryHanler MarryEvent;
static void Main(string[] args)
{
}
}
}
Reflector反编译的结果:
图1
图2
图3
图4
可以看出,C#事件被编译成包含两个公共方法的代码段,一个带有add_前缀,另一个带有remove_前缀,前缀后面是C#事件的名称。
在add_方法中,通过调用了Delegate.Combine()方法来实现的(图3中红框的地方),Delegate.Combine()方法将多个委托组合为了一个多路广播委托。
在remove_方法中,同样采用了Delegate.Remove()方法。
由上面的四张图中可以总结出:
C#的事件是一个特殊的多路广播委托,事件默认含有一个私有的委托类型变量(图2的红框),该变量用于保存对事件处理方法的引用,且该委托类型的变量为私有,只能从定义该事件的类中进行访问。
从反编译的代码中可以看出跟我们学过的属性是相似的。但与事件不同,属性中定义了set访问和get访问器,两个访问器的本质就是以"get_"和"set_"为前缀的两个方法。属性用于对类中的私有字段进行访问,而C#事件也可以看作是“委托字段的属性”,因此可以通过事件来对私有的委托字段进行访问,这也是C#事件特性存在的原因。C#事件机制符合面向对象的封装特性,是代码更安全。