也谈事件---Mom,Baby and you[一,二,三,四]
朋友家有一个宝宝饿的时候总是喜欢哭闹,想尿尿的时候就开始哼哼唧唧或是手舞足蹈。
如果大人够细致,就能分辨出宝宝的这种哭闹是饿了,这可以叫做饥饿性哭闹。
同理,这种哼哼唧唧或是手舞足蹈也是在通知大人他想尿尿了。
上面的这两个过程就是我要说的事件应用场景,咱们慢慢模拟这一场景:
一、Mom忙碌的一天
最简单的思路,当孩子哭闹和哼哼唧唧的时候,妈妈要有响应,那么我们可能要定义下面这几项:
(1)事件委托,名称为:事件名+EventHandler。
(2)事件成员,名称为前面的委托名去掉EventHandler。
于是乎我们写出来下面的Baby:
{
//(1)定义事件委托
public delegate void CryEventHandler();
public delegate void CroonEventHandler();
//(2)定义事件成员
public event CryEventHandler Cry;//饥饿性哭闹
public event CroonEventHandler Croon;//哼哼唧唧
public void Action()
{
Console.WriteLine("CryCry");
//调用委托时进行null检查
if (Cry != null)
{
Cry();
}
Console.WriteLine("CroonCroon");
if (Croon != null)
{
Croon();
}
}
}
和下面的Mom:
{
public void Action()
{
Baby baby = new Baby();
baby.Cry += new Baby.CryEventHandler(baby_Cry);
baby.Croon += new Baby.CroonEventHandler(baby_Croon);
baby.Action();
}
public void baby_Cry()
{
Console.WriteLine("妈妈开始给baby准备食物");
}
public void baby_Croon()
{
Console.WriteLine("妈妈抱起baby嘘嘘");
}
}
看看有什么问题,没曾想宝宝还有个小妹妹。
YES!他们是双胞胎^_^。上面写的东西肯定是不满足了。
因为我们分辨不出是哪个宝宝在哭闹或想嘘嘘。
二、你和两个Baby
前面说了,这个Mom有两个Baby。我们前面的实现太粗糙了。
假设您现在正在这位Mom家里做客,这个时候Mom正在为您准备一顿大餐。
照顾小孩的责任一下落在了您的头上。如果您和Mom一家很熟,Mom可能会告诉您
“小强哭两声就是饿了。小丽哭一声就是饿了。
小强哼哼两声就是要嘘嘘了,小丽哼哼一声就是要嘘嘘了”
如果您和Mom一家不是很熟,Mom可能会告诉您
“男孩哭两声就是饿了。女孩哭一声就是饿了。
男孩哼哼两声就是要嘘嘘了,女孩哼哼一声就是要嘘嘘了”
我想,您已经明白Mom的意思了。
通过参考委托和事件的定义规范我们总结出这么几条:
(1)事件委托名称应以 EventHandler 结尾。
(2)事件委托应该有两个参数:
第一个是 object 类型的 sender,代表发出事件通知的对象。
第二个参数 e,应该是 EventArgs 或其派生类型。
(3)事件参数类型,应从 EventArgs 继承,名称应以 EventArgs 结尾。应将所有想通过事件传达到外界的信息都放在事件参数 e 中。
(4)应该为每个事件提供一个 protected virtual的 OnXxxx 方法:方法名称为 On 加上事件的名称;只有一个事件参数 e;
并在该方法中进行 null 判断。 在需要发出事件通知的地方,调用OnXxxx 方法。
于是有了下面的Baby
{
//Cry事件参数
public class CryEventArgs : EventArgs
{
private string _name = string.Empty;
private string _sex = string.Empty;
private string _voice = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string Voice
{
get { return _voice; }
set { _voice = value; }
}
public CryEventArgs(string name,string sex,string voice)
{
_name = name;
_sex = sex;
_voice = voice;
}
}
//Croon事件参数
public class CroonEventArgs : EventArgs
{
private string _name = string.Empty;
private string _sex = string.Empty;
private string _hmm = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string Hmm
{
get { return _hmm; }
set { _hmm = value; }
}
public CroonEventArgs(string name, string sex, string hmm)
{
_name = name;
_sex = sex;
_hmm = hmm;
}
}
//定义事件委托
public delegate void CryEventHandler(object sender, CryEventArgs e);
public delegate void CroonEventHandler(object sender, CroonEventArgs e);
//定义事件成员
public event CryEventHandler Cry;
public event CroonEventHandler Croon;
//受保护虚方法
protected virtual void OnCry(CryEventArgs e)
{
if (Cry != null)
{
Cry(this, e);
}
}
protected virtual void OnCroon(CroonEventArgs e)
{
if (Croon != null)
{
Croon(this, e);
}
}
public void Action()
{
Console.WriteLine("CryCry");
OnCry(new CryEventArgs("小强","男孩","哭了两声"));
Console.WriteLine("CroonCroon");
OnCroon(new CroonEventArgs("小强", "男孩", "哼哼了两声"));
}
}
和下面的您:
{
public void Action()
{
Baby2 baby = new Baby2();
baby.Cry += new Baby2.CryEventHandler(baby_Cry);
baby.Croon += new Baby2.CroonEventHandler(baby_Croon);
baby.Action();
}
private void baby_Cry(object sender, Baby2.CryEventArgs e)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + e.Name +e.Voice);
Console.WriteLine("您开始给baby准备食物");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + e.Sex + e.Voice);
Console.WriteLine("您开始给baby准备食物");
}
private void baby_Croon(object sender, Baby2.CroonEventArgs e)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + e.Name +e.Hmm);
Console.WriteLine("您抱起baby嘘嘘");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + e.Sex + e.Hmm);
Console.WriteLine("您抱起baby嘘嘘");
}
}
故事到这里还没有结束,因为还会有下面的这种情况出现:
三、Baby的无奈
前面事件声明时,无论是否有事件处理程序挂接,它都会占用一定的内存空间。
也就是说,小强哭闹以后,您给他吃东西了。他吃完东西又要嘘嘘了。
但这个时候您也去嘘嘘了,您不在小强的身边,所以小强要嘘嘘的动作没有人来响应。
使用下面的方法。在Baby类中将不创建Croon事件,就是说不让小强哼哼了,因为哼哼了也没用^_^
所以我们提供类似属性的事件声明,事件声明使用add,remove访问器:
public event [委托类型] [事件名称]
{
add { .... }
remove { .... }
}
修改Baby类如下:
{
//Cry事件参数
public class CryEventArgs : EventArgs
{
private string _name = string.Empty;
private string _sex = string.Empty;
private string _voice = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string Voice
{
get { return _voice; }
set { _voice = value; }
}
public CryEventArgs(string name, string sex, string voice)
{
_name = name;
_sex = sex;
_voice = voice;
}
}
//Croon事件参数
public class CroonEventArgs : EventArgs
{
private string _name = string.Empty;
private string _sex = string.Empty;
private string _hmm = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Sex
{
get { return _sex; }
set { _sex = value; }
}
public string Hmm
{
get { return _hmm; }
set { _hmm = value; }
}
public CroonEventArgs(string name, string sex, string hmm)
{
_name = name;
_sex = sex;
_hmm = hmm;
}
}
/// <summary>
/// 定义事件委托
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void CryEventHandler(object sender, CryEventArgs e);
public delegate void CroonEventHandler(object sender, CroonEventArgs e);
// 为每种事件生成一个唯一的 object 作为键
static readonly object CryEventKey = new object();
static readonly object CroonEventKey = new object();
//存储事件处理程序
protected Dictionary<object, Delegate> dict = new Dictionary<object, Delegate>();
//private Hashtable dict = new Hashtable();
//向dict中添加事件
protected void AddEventHandler(object eventKey, Delegate handler)
{
lock (this)
{
if (!dict.ContainsKey(eventKey))
{
dict.Add(eventKey, handler);
}
else
{
dict[eventKey] = handler;
}
}
}
//从dict中移除事件
protected void RemoveEventHandler(object eventKey)
{
lock (this)
{
dict.Remove(eventKey);
}
}
//从dict中获得事件
protected Delegate GetEventHandler(object eventKey)
{
if (!dict.ContainsKey(eventKey))
return null;
else
return (Delegate)dict[eventKey];
}
//定义事件成员
public event CryEventHandler Cry
{
add { AddEventHandler(CryEventKey, value); }
remove { RemoveEventHandler(CryEventKey); }
}
public event CroonEventHandler Croon
{
add { AddEventHandler(CroonEventKey, value); }
remove { RemoveEventHandler(CroonEventKey); }
}
//受保护虚方法
protected virtual void OnCry(CryEventArgs e)
{
CryEventHandler Cry = (CryEventHandler)GetEventHandler(CryEventKey);
if (Cry != null)
{
Cry(this, e);
}
}
protected virtual void OnCroon(CroonEventArgs e)
{
CroonEventHandler Croon = (CroonEventHandler)GetEventHandler(CroonEventKey);
if (Croon != null)
{
Croon(this, e);
}
}
public void Action()
{
Console.WriteLine("CryCry");
OnCry(new CryEventArgs("小强", "男孩", "哭了两声"));
Console.WriteLine("CroonCroon");
OnCroon(new CroonEventArgs("小强", "男孩", "哼哼了两声"));
}
}
实际上,我们只是用一个Dictionary
存储外部挂接上的事件处理程序。通过add和remove操作实现了事件的增加和删除。
简单的修改一下您,因为Baby Croon的时候您去嘘嘘了:
{
public void Action()
{
Baby3 baby = new Baby3();
baby.Cry += new Baby3.CryEventHandler(baby_Cry);
//baby.Croon += new Baby3.CroonEventHandler(baby_Croon);
baby.Action();
}
private void baby_Cry(object sender, Baby3.CryEventArgs e)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + e.Name + e.Voice);
Console.WriteLine("您开始给baby准备食物");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + e.Sex + e.Voice);
Console.WriteLine("您开始给baby准备食物");
}
private void baby_Croon(object sender, Baby3.CroonEventArgs e)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + e.Name + e.Hmm);
Console.WriteLine("您抱起baby嘘嘘");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + e.Sex + e.Hmm);
Console.WriteLine("您抱起baby嘘嘘");
}
}
四、接口实现
再补充一个接口实现,思路是引入一个接口实现回调。
下面是Baby及接口实现:
{
void Cry(string name,string sex,string voice);
void Croon(string name,string sex,string hmm);
void Other(string name, string sex, string active);
}
public class BabyAdapter : IBaby
{
public virtual void Cry(string name, string sex, string voice) { }
public virtual void Croon(string name, string sex, string hmm) { }
public virtual void Other(string name, string sex, string active) { }
}
public class Baby4
{
private BabyAdapter b = null;
public Baby4(BabyAdapter b)
{
this.b = b;
}
public void Action()
{
Console.WriteLine("CryCry");
b.Cry("小强", "男孩", "哭了两声");
Console.WriteLine("CroonCroon");
b.Croon("小强", "男孩", "哼哼了两声");
}
}
之所以使用adapter类而不是直接继承自接口是因为外界不必实现接口中的所有方法。
下面就是YOU:
{
private class MyBaby:BabyAdapter
{
public override void Cry(string name,string sex,string voice)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + name + voice);
Console.WriteLine("您开始给baby准备食物");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + sex + voice);
Console.WriteLine("您开始给baby准备食物");
}
public override void Croon(string name, string sex, string hmm)
{
Console.WriteLine("如果您是Mom的熟人,你会这样分析:" + name + hmm);
Console.WriteLine("您抱起baby嘘嘘");
Console.WriteLine("如果您不是Mom的熟人,你会这样分析:" + sex + hmm);
Console.WriteLine("您抱起baby嘘嘘");
}
}
public void Action()
{
Baby4 baby = new Baby4(new MyBaby());
baby.Action();
}
}