C#高级编程之事件
在介绍事件之前,我们先讲解委托,然后由委托衍生讲解事件。
现有这样的需求:要求在猫叫之后,执行狗哭,老鼠跑,孩纸哭的动作。
初步的实现如下:
class Program { static void Main(string[] args) { Cat cat = new Cat(); cat.Miao(); } }
public void DogSaying() { Console.WriteLine("the dog is barking"); }
public void BabyCry() { Console.WriteLine("the baby is crying"); }
public void MouseRunning() { Console.WriteLine("the mouse is running"); }
public void Miao() { Console.WriteLine("cat miaoing"); new Dog().DogSaying(); new Mouse().MouseRunning(); new Baby().BabyCry(); }
执行这个方法后,我们会发现这个Miao叫方法依赖的类型太多了,依赖于Dog、Mouse、Baby类。如果任何一个环节发生改变,这个方法就要修改,导致这个方法非常不稳定。其实也不应该这样,猫应该就只是执行Miao一下的功能,单一职责。
此时可以采用多播委托的方式:
基于多播委托的观察者模式
客户端添加如下代码:
Console.WriteLine("*************多播委托************"); cat.CatMiaoAction += new Dog().DogSaying; cat.CatMiaoAction += new Baby().BabyCry; cat.CatMiaoAction += new Mouse().MouseRunning; cat.MiaoDelegate();
Cat类中添加如下代码:
public void Miao() { Console.WriteLine("cat miaoing"); MiaoDelegate(); } public Action CatMiaoAction; /// <summary> /// 基于委托的观察者模式(C#) /// 把需要执行的动作放到委托中去 /// </summary> public void MiaoDelegate() { Console.WriteLine($"{this.GetType().Name} Miao");//固定动作 CatMiaoAction?.Invoke(); }
基于抽象接口的观察者模式
定义一个IObject接口,这些动物都要实现接口中的void DoAction();方法。
namespace DelegateObserverDemo { class Baby: IObject { public void BabyCry() { Console.WriteLine("the baby is crying"); } public void DoAction() { this.BabyCry(); } } }
namespace DelegateObserverDemo { class Dog:IObject { public void DoAction() { this.DogSaying(); } public void DogSaying() { Console.WriteLine("the dog is barking"); } } }
客户端代码如下:
static void Main(string[] args) { Cat cat = new Cat(); Console.WriteLine("*************观察者模式*************"); cat.AddObserver(new Dog()); cat.AddObserver(new Baby() ); cat.AddObserver(new Mouse()); cat.MiaoAbserver();
}
Cat类代码:
/// <summary> /// 基于面向抽象的观察者模式 /// </summary> private List<IObject> _Observer = new List<IObject>(); public void AddObserver(IObject observer) { _Observer.Add(observer); } public void MiaoAbserver() { Console.WriteLine($"{this.GetType().Name} Miao"); foreach (var item in _Observer) { item.DoAction(); } }
对比cat.AddObserver(new Dog())与委托中+=功能,可以发现两者还是有很多相同的地方。
那能否通过事件来实现相同的功能呢?答案是可以的。
委托与事件的区别
委托实例.Invoke(参数列表)执行委托绑定的方法
Cat中代码如下:
//事件Event:就是一个委托的实例,加了一个Event关键字 //事件实现了上面的需求 //event:可以限定权限 public event Action CatMiaoActionHandler; /// <summary> /// 基于委托的观察者模式(C#) /// 把需要执行的动作放到委托中去
///通过委托实例(亦即事件).Invoke(参数列表)触发委托 /// </summary> public void MiaoEvent() { Console.WriteLine($"{this.GetType().Name} MiaoEvent");//固定动作 CatMiaoActionHandler?.Invoke(); Console.WriteLine($"{this.GetType().Name} aaa");//固定动作 Console.WriteLine($"{this.GetType().Name} bbb");//固定动作 }
此处只是对CatMiaoAction加了个event关键字,取了个名字CatMiaoActionHandler;同时修改原来的MiaoDelegate函数名为MiaoEvent。
C#声明委托:
public delegate void Action();
public event Action CatMiaoActionHandler;委托Action是一个类型(类比string,int,那此处就是public event string CatMiaoActionHandler),
所以:[此处事件CatMiaoActionHandler是委托Action类型的一个实例,并用event关键字描述其为一个事件]
客户端如下:
Console.WriteLine("*************事件Event************"); cat.CatMiaoActionHandler += new Dog().DogSaying; cat.CatMiaoActionHandler += new Baby().BabyCry; cat.CatMiaoActionHandler += new Mouse().MouseRunning; cat.MiaoEvent();
可以看到与客户端执行委托相关方法没什么区别。但是上面委托的实现中我们在客户端可以对CatMiaoAction进行相关操作。
cat.CatMiaoAction = null;
cat.CatMiaoAction.Invoke();
但是执行cat.CatMiaoActionHandler = null;就会报错
可以看到event是限定了权限的,其只能在事件所在类的内部invoke;在外面是不行的,包括子类也不行。
class ChildCat : Cat
{
public void Show()
{
//this.CatMiaoActionHandler = null;这句也会报错,故在子类中修改、操作也不行。
}
}
委托实例【事件】(参数列表)执行委托绑定的方法
除了上面的方式触发委托外,还有通过委托实例(参数列表)【即事件名(参数列表)】的方式调用触发委托,如下:
客户端代码:
Console.WriteLine("*************事件Event,通过委托实例(参数列表)【即事件名(参数列表)】的方式调用************"); cat.CatMiaoActionHandler += new Dog().DogSaying; cat.CatMiaoActionHandler += new Baby().BabyCry; cat.CatMiaoActionHandler += new Mouse().MouseRunning; cat.MiaoEventAnotherMethod();
///通过委托实例(参数列表)【即事件名(参数列表)】的方式调用触发委托 public void MiaoEventAnotherMethod() { Console.WriteLine($"{this.GetType().Name} MiaoEvent");//固定动作 CatMiaoActionHandler(); Console.WriteLine($"{this.GetType().Name} aaa");//固定动作 Console.WriteLine($"{this.GetType().Name} bbb");//固定动作 }
联想到winForm中我们看到的各种事件,好像还差那么点意思。。别急。请看下面:
内置的EventHandler委托如下,该委托包含两个参数sender,e分别:
// 摘要: // 表示将用于处理不具有事件数据的事件的方法。 // // 参数: // sender: // 事件源。 // // e: // 不包含事件数据的对象。 [ComVisible(true)] public delegate void EventHandler(object sender, EventArgs e);
与其相关Click事件,其为EventHandler委托的实例:
public event EventHandler Click;
现在用该委托的实例 MyEvent 执行其绑定的方法
public class Example { public event EventHandler myEvent; internal void Go() { object sender = 10; EventArgs e = new EventArgs(); //为事件注册多个委托 myEvent += Print; myEvent += Say; myEvent(sender, e); } void Print(object sender, EventArgs e) { Console.WriteLine(sender); } void Say(object sender, EventArgs e) { Console.WriteLine(sender); } }
总结
委托是一个类,是MultiCastDelegate基类的子类。
事件是委托的一个实例;事件比委托更加安全,其只能在声明当前该事件的类的内部进行触发(不管是Invoke发方式还是委托实例【事件】(参数列表)执行委托绑定的方法),即使在子类也不行。