事件
1 请解释事件的基本使用方法
2 事件和委托有何联系
3 如何设计一个带有很多事件的类型
4 用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒
事件时一种使对象或类能够提供通知的成员。客户端可以通过操作事件处理程序为相应的事件添加可执行代码。事件是一种特殊的委托。
设计和使用事件的全过程可能需要包含下列几个步骤:
- 如果需要的话,定义一个派生自System.EventArgs的参数类型
- 在事件的管理类型中定义事件私有成员
- 通知事件订阅者
- 事件使用客户端订阅/取消定义
示例:控制台输出事件管理和使用的示例
首先定义一个控制台时间参数类型
/// <summary> /// 自定义一个事件参数类型 /// </summary> public class ConsoleEventArgs : EventArgs { //控制台输出的消息 private String _message; /// <summary> /// 构造方法 /// </summary> public ConsoleEventArgs():base() { _message = String.Empty; } /// <summary> /// 构造方法 /// </summary> public ConsoleEventArgs(String message) : base() { _message = message; } /// <summary> /// 只读属性 /// </summary> public String Message { get { return _message; } } }
随后编写控制台事件的管理类型:
/// <summary> /// 管理控制台,在输出前发送输出事件 /// </summary> public class ConsoleManager { //定义控制台事件成员对象 public event EventHandler<ConsoleEventArgs> ConsoleEvent; /// <summary> /// 控制台输出 /// </summary> /// <param name="message">用来构造事件参数</param> public void ConsoleOutput(String message) { //先发送事件 ConsoleEventArgs args = new ConsoleEventArgs(message); SendConsoleEvent(args); //输出 Console.WriteLine(message); } /// <summary> /// 负责发送事件 /// </summary> /// <param name="args">事件参数</param> protected virtual void SendConsoleEvent(ConsoleEventArgs args) { //定义一个临时的引用变量,这样可以确保多线程访问时不会发生问题 EventHandler<ConsoleEventArgs> temp = ConsoleEvent; if (temp != null) temp(this, args); } }
为了演示事件的订阅,定义了一个订阅控制台事件的日志类型,当控制台事件发生时,该类型对象将向对应的日志文件写入控制台输出的内容。
/// <summary> /// 日志类型,订阅控制台输出事件 /// </summary> public class Log { private const String LogFile="C:\TestLog.txt"; public Log(ConsoleManager cm) { //订阅控制台输出事件 cm.ConsoleEvent += WriteLog; } /// <summary> /// 事件处理方法,注意参数固定模式 /// </summary> /// <param name="send">事件发送者</param> /// <param name="args">事件参数</param> private void WriteLog(object send, EventArgs args) { if(!File.Exists(LogFile)) { using (FileStream fs = File.Create(LogFile)) { } } FileInfo info = new FileInfo(LogFile); using (StreamWriter sw = info.AppendText()) { sw.WriteLine(DateTime.Now.ToString() + "|" + send.ToString() + "|" + ((ConsoleEventArgs)args).Message); } } }
调用:
class ConsoleEvent { static void Main(string[] args) { //测试事件 ConsoleManager cm = new ConsoleManager(); Log log = new Log(cm); cm.ConsoleOutput("测试控制台输出事件"); cm.ConsoleOutput("测试控制台输出事件"); cm.ConsoleOutput("测试控制台输出事件"); Console.Read(); } }
事件是一个委托类型。用Reflector查看上面例子的C#代码
ConsoleEvent的反编译代码为:
public event EventHandler<ConsoleEventArgs> ConsoleEvent { add { //省略…… } remove { //省略…… } }
而EventHandler<ConsoleEventArgs>的定义为则为一个泛型委托:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
事件有两个方法add_xxx和remove_xxx,分别用来注册和注销事件。
查看上例中Log的构造函数的代码:
public Log(ConsoleManager cm) { cm.ConsoleEvent += new EventHandler<ConsoleEventArgs>(this.WriteLog); }
对应的IL代码为:
.method public hidebysig specialname rtspecialname instance void .ctor(class MyTest.ConsoleManager cm) cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() L_0006: nop L_0007: nop L_0008: ldarg.1 L_0009: ldarg.0 L_000a: ldftn instance void MyTest.Log::WriteLog(object, class [mscorlib]System.EventArgs) L_0010: newobj instance void [mscorlib]System.EventHandler`1<class MyTest.ConsoleEventArgs>::.ctor(object, native int) L_0015: callvirt instance void MyTest.ConsoleManager::add_ConsoleEvent(class [mscorlib]System.EventHandler`1<class MyTest.ConsoleEventArgs>) L_001a: nop L_001b: nop L_001c: ret }
当某个类型包含较多的事件时,可以考虑把所有事件的委托链存储在一个集合之中,这样能做到有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。.NET的内建类型System.ComponentModel.EventHandlerList提供了这样一个存储事件集合的封装。
示例:首先定义一个包含大量事件的类型,它使用一个EventHandlerList成员来存储所有事件,在类型构造和析构的时候需要对该成员初始化和析构
/// <summary> /// 多事件类型 /// </summary> public partial class MultiEventsClass:IDisposable { /// <summary> /// System.ComponentModel.EventHandlerList包含了一个委托链表的容器 /// 实现了多事件存放在一个容器之中的包装 /// EventHandlerList使用的是链表数据结构 /// </summary> private EventHandlerList _events; //公共构造方法 public MultiEventsClass() { _events = new EventHandlerList(); } /// <summary> /// 释放EventHanlderList /// </summary> public void Dispose() { _events.Dispose(); } }
为了演示,这里只声明两个事件,而在实际情况中,多数事件类型可能会包含数十个事件
/// <summary> /// 多事件类型 /// </summary> public partial class MultiEventsClass : IDisposable { //下面为每一个需要实现的事件申明委托类型、订阅和取消定语方法、事件在集合中的键和触发事件方法 //这样的定义和实际申明一个事件成员不同,这样做不会在一个新的MultiEventsClass中分配所有的事件委托链表的内存空间 //这就是提高性能的关键 //申明事件1 #region event1 //事件1的委托原型 public delegate void Event1Handler(Object sender, EventArgs e); //这里是静态的字段,有效提高性能 protected static readonly Object Event1Key = new object(); /// <summary> /// 一组订阅、取消订阅事件的方法 /// 注意EventHandlerList并不提供线程同步,所以在add和remove方法前加上线程同步属性 /// 读者可以采取lock机制来代替 /// </summary> public event Event1Handler Event1 { [MethodImpl(MethodImplOptions.Synchronized)] add { _events.AddHandler(Event1Key, value); } [MethodImpl(MethodImplOptions.Synchronized)] remove { _events.RemoveHandler(Event1Key, value); } } /// <summary> /// 触发事件1 /// </summary> /// <param name="e"></param> protected virtual void OnEvent1(EventArgs e) { _events[Event1Key].DynamicInvoke(this, e); } /// <summary> /// 这个方法简单地触发事件1,以便于测试 /// </summary> public void RiseEvent1() { OnEvent1(EventArgs.Empty); } #endregion //申明事件2 #region event2 //事件2的委托原型 public delegate void Event2Handler(Object sender, EventArgs e); //这里是静态的字段,有效提高性能 protected static readonly Object Event2Key = new object(); /// <summary> /// 一组订阅、取消订阅事件的方法 /// 注意EventHandlerList并不提供线程同步,所以在add和remove方法前加上线程同步属性 /// 读者可以采取lock机制来代替 /// </summary> public event Event2Handler Event2 { [MethodImpl(MethodImplOptions.Synchronized)] add { _events.AddHandler(Event2Key, value); } [MethodImpl(MethodImplOptions.Synchronized)] remove { _events.RemoveHandler(Event2Key, value); } } /// <summary> /// 触发事件2 /// </summary> /// <param name="e"></param> protected virtual void OnEvent2(EventArgs e) { _events[Event2Key].DynamicInvoke(this, e); } /// <summary> /// 这个方法简单地触发事件2,以便于测试 /// </summary> public void RiseEvent2() { OnEvent2(EventArgs.Empty); } #endregion }
构造一个事件订阅类型:
/// <summary> /// 构造一个订阅事件的类型 /// </summary> public class Customer { public Customer(MultiEventsClass events) { //订阅事件1 events.Event1 += Event1Handler; //订阅事件2 events.Event2 += Event2Handler; } /// <summary> /// 事件1回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void Event1Handler(object sender, EventArgs args) { Console.WriteLine("事件1触发"); } /// <summary> /// 事件2回调方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void Event2Handler(object sender, EventArgs args) { Console.WriteLine("事件2触发"); } }
调用:
class MainClass { static void Main(string[] args) { //测试事件的触发 using (MultiEventsClass c = new MultiEventsClass()) { Customer customer = new Customer(c); c.RiseEvent1(); c.RiseEvent2(); } Console.Read(); } }
输出:
事件1触发
事件2触发
设计思路:猫类负责并维护一个猫叫事件,主人和老鼠则需要订阅猫叫这一事件,这样能够保证猫叫时执行相应的动作。
首先设计猫的类型,该类型是猫叫事件的管理者。这里为猫叫事件定义了一个参数类型,来说明发出声音的猫的名字:
#region cat /// <summary> /// 猫类型 /// 维护猫叫事件 /// </summary> public class Cat { /// <summary> /// 猫名 /// </summary> private String _name; /// <summary> /// 猫叫的事件 /// </summary> public event EventHandler<CatCryEventArgs> CatCryEvent; /// <summary> /// 构造方法 /// </summary> /// <param name="name"></param> public Cat(String name) { _name = name; } /// <summary> /// 触发猫叫事件 /// </summary> public void CatCry() { CatCryEventArgs args = new CatCryEventArgs(_name); Console.WriteLine(args); CatCryEvent(this, args); } } /// <summary> /// 猫叫事件的参数 /// </summary> public class CatCryEventArgs : EventArgs { //发出叫声的猫的名字 private String _catname; public CatCryEventArgs(String catname) : base() { _catname = catname; } /// <summary> /// 输出参数内容 /// </summary> /// <returns></returns> public override string ToString() { return _catname + "叫了"; } } #endregion
然后是主人和老鼠类型定义,这两个类型都是猫叫事件的订阅者。
#region Master /// <summary> /// 主人类型 /// </summary> public class Master { /// <summary> /// 主人名字 /// </summary> private String _name; /// <summary> /// 构造方法,订阅事件 /// </summary> /// <param name="name"></param> /// <param name="cat"></param> public Master(String name, Cat cat) { _name = name; cat.CatCryEvent += CatCryHandler; } /// <summary> /// 猫叫事件处理方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void CatCryHandler(object sender, CatCryEventArgs args) { WakeUp(); } /// <summary> /// 惊醒方法 /// </summary> private void WakeUp() { Console.WriteLine(_name + "醒了"); } } #endregion #region mouse /// <summary> /// 老鼠类型 /// </summary> public class Mouse { /// <summary> /// 老鼠名字 /// </summary> private String _name; public Mouse(String name, Cat cat) { _name = name; cat.CatCryEvent += CatCryHandler; } /// <summary> /// 猫叫事件处理方法 /// </summary> /// <param name="sender"></param> /// <param name="args"></param> private void CatCryHandler(object sender, CatCryEventArgs args) { Run(); } /// <summary> /// 逃跑方法 /// </summary> private void Run() { Console.WriteLine(_name + "逃走了"); } } #endregion
调用:
class MainClass { static void Main(string[] args) { //开始模拟场景 Console.WriteLine("开始模拟"); Cat cat = new Cat("汤姆猫"); Mouse mouse1 = new Mouse("米老鼠", cat); Mouse mouse2 = new Mouse("杰瑞鼠", cat); Master master = new Master("Jesse", cat); cat.CatCry(); Console.Read(); } }
输出:
开始模拟
汤姆猫叫了
米老鼠逃走了
杰瑞鼠逃走了
Jesse醒了
转载请注明出处: