您的位置:首页 > 其它

.NET基础 (16)事件

2015-09-11 17:49 316 查看
事件
1 请解释事件的基本使用方法
2 事件和委托有何联系
3 如何设计一个带有很多事件的类型
4 用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

事件
1 请解释事件的基本使用方法

事件时一种使对象或类能够提供通知的成员。客户端可以通过操作事件处理程序为相应的事件添加可执行代码。事件是一种特殊的委托。

设计和使用事件的全过程可能需要包含下列几个步骤:

如果需要的话,定义一个派生自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();
}
}


2 事件和委托有何联系

事件是一个委托类型。用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
}


3 如何设计一个带有很多事件的类型

当某个类型包含较多的事件时,可以考虑把所有事件的委托链存储在一个集合之中,这样能做到有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。.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触发

4 用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

设计思路:猫类负责并维护一个猫叫事件,主人和老鼠则需要订阅猫叫这一事件,这样能够保证猫叫时执行相应的动作。

首先设计猫的类型,该类型是猫叫事件的管理者。这里为猫叫事件定义了一个参数类型,来说明发出声音的猫的名字:

#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醒了

转载请注明出处:

作者:JesseLZJ
出处:http://jesselzj.cnblogs.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: