前言
在日常开发业务中会经常使用到事件,本文将通过一个个实例讲解事件是什么,事件如何使用。
一、事件是什么?
(1) 事件是一种特殊的委托,本质上来讲事件就是委托。
(2)如果说委托是对方法的包装,那么事件就是对委托进一步的包装,提升了使用的安全性,事件是安全版的委托
【理解这个很重要】为何说事件是安全版的委托呢?
1 委托可以定义在一个类的外部也可以定义在内部,任何地方都可以声明委托,对委托进行添加或移除方法和调用等操作。使用的时候是很方便,但是安全性就难以保证。
比如:
在一个类中调用委托的时候本想做指定的事情,但是在另外一个类被添加了新的方法,或者被赋值为null,那么本想做的事情就遭到了破坏,委托好用,但是太过于”开放“了,那么有了代码的安全性问题。
*2 事件的使用规定:事件只能在当前类被访问,子类和类外部均不能执行类中的事件方法
解析:
a.外部类不可能使用xxxEvent() 或 xxxEvent.Invoke()的方式去调用事件
b.事件只能在当前类被调用,外部要使用事件,要先实例化当前类才可以,然后当前类定义一个调用事件的方法。外部通过实例化当前类,然后调用类中方法的方式,调用事件了。
c 外部类只能通过+=或者-=的方式对事件进行添加和移除方法的操作,虽然能添加移除方法,但是调用权在当前类
通过以上3点足以说明事件的安全性
二、事件的使用
案例:猫叫了,老鼠听见了就跑…
//【1】声明委托,委托声明一般是以EventHandlder结尾的方式命名
public delegate void CatMiaoEventHanlder();
public class Cat
{
//【2】声明一个事件,--只能在类内部声明
public event CatMiaoEventHanlder CatMiaoEvent;
// CatMiaoEventHanlder CatMiaoEventHanlder;
// 这里注意比对申明委托的变量,事件和委托,
// 只不过是新增了一个event关键字,表示他是一个特殊的委托=》事件
//【3】-1 定义对应事件的方法,后面需要注册到事件中
public void Miao()
{
Console.WriteLine("喵,喵,喵,喵!");
}
//【4】类内部定义一个调用事件的方法
public void MiaoEvent()
{
CatMiaoEvent?.Invoke();
}
}
public class Mouse
{
//【3】-2 定义对应事件的方法,后面需要注册到事件中
public void Run()
{
Console.WriteLine("赶紧跑,猫猫来了!");
}
}
static void Main(string[] args)
{
//【5】调用事件,完成猫叫后,小老鼠就跑的这一事件
Cat cat = new Cat();
cat.CatMiaoEvent += cat.Miao;
cat.CatMiaoEvent += new Mouse().Run;
cat.CatMiaoEvent += () => { Console.WriteLine("狗也跟着叫了"); };//临时加一个听见猫叫狗也叫的动作
cat.MiaoEvent();//最后调用,完成一系列动作
}
通过以上可知:使用事件有
(1)申明委托
(2)在当前类针对委托定义事件
(3)在需要与事件有关联的类中 编写事件对应的方法,提供后续调用
(例如猫叫了,老鼠听见了需要跑,这就是关联,需要在老鼠的类中写跑的方法)
(4)在当前类中定义调用事件的方法,供外部类调用
(5)在外部调用,实例化当前类,注册与事件相关的方法,然后调用事件,完成一系列动作
三、 WinForm中的事件
关于WinForm中的事件我们需要了解以下内容:
在WinForm当我们双击Button会在后台创建了一个的Button_Click事件,
this.button1.Click += new System.EventHandler(this.button1_Click);
什么是EventHandler呢?
public delegate void EventHandler(object sender, EventArgs e);
其实就是一个系统已经定义好的委托
对于WinForm来说,这个object sender 传递过来的是什么呢?EventArgs又是什么呢?
当前是Button的事件,那么传入进来的object sender 就是Button自身,如果是Form窗体的事件,那么sender 这个就是窗体本身
EventArgs就是与这个事件相关的一些参数,比如位置啊,点击次数啊等等,该参数可以为null
四、EventHandler详解&订阅模式
public delegate void EventHandler(object sender, EventArgs e);
EventHandler就是系统已经定义好的事件(或叫委托),如果我们使用自定义的事件(或叫委托)是不需深究object 和EventArgs这两个入参的,反之,需要使用,都得好好深究一下。
首先我们需要知道,EventArgs是事件参数的基类,我们使用EventHandler事件的时候,如果不需要传递参数,这个可以直接设为null,但是如果需要传入参数,那么传入参数须是EventArgs类型或者EventArgs的派生类
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher() { Name="李发布"};
publisher.PublishEvent += new Subscriber1() { Name = "张订阅" }.Action;
publisher.PublishEvent += new Subscriber2() { Name = "王订阅" }.Action2;
publisher.OnPublishEvent(new Book() { Name="平凡的世界"});
Console.ReadLine();
}
}
//事件发布者
public class Publisher
{
public string Name { get; set; }
//定义事件
public event EventHandler<Book> PublishEvent;
//定义调用事件的方法
public void OnPublishEvent(Book book)
{
//如果实际处理的业务中,不需要入参,object sender 和EventArg e 都可以传入null
PublishEvent?.Invoke(this,book);
}
}
//事件订阅者1
public class Subscriber1
{
public string Name { get; set; }
//定义 订阅方法,当触发发布事件的时候就调用该方法
public void Action(object sender,Book book)
{
if (sender is Publisher publisher)
{
Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
}
}
}
//事件订阅者2
public class Subscriber2
{
public string Name { get; set; }
public void Action2(object sender, Book book)
{
if (sender is Publisher publisher)
{
Console.WriteLine($"发布者:{publisher.Name},订阅者{Name}:订阅了{book.Name}");
}
}
}
//EventArgs是EventHandler中传入参数的基类,因此需要传递参数的时候,参数都需要继承自EventArgs
public class Book:EventArgs
{
public string Name { get; set; }
}
五、综合案例
需求:汽车出停车场时收费,开闸放行
class Program
{
static void Main(string[] args)
{
//先分析需求:车到车库门口,摄像机要拍照到车牌后,收费员收费,闸机抬杆
Camera camera = new Camera();
Charger charger = new Charger();
Gate gate = new Gate();
camera.OnSnapLicenseEvent += charger.Charge;
camera.OnSnapLicenseEvent += gate.OpenGate;
camera.SnapPhoto();
Console.ReadLine();
}
}
public class Camera
{
//vs版本较低,无法使用Task,这里使用thread
public event EventHandler<CarInfo> OnSnapLicenseEvent;
//模拟摄像机一致在抓拍车牌
public void SnapPhoto()
{
Thread thread = new Thread(() =>
{
List<string> licenses = new List<string>()
{ "", "", "", "沪A11111", "沪B22222","沪C33333","","" };
Random random = new Random();
while (true)
{
Thread.Sleep(1000);
int index = random.Next(1, licenses.Count + 1);
SnapInfo snapInfo = new SnapInfo() { LicensePlate = licenses[index-1] };
//当车牌不为空的时候表示车来了
if (!string.IsNullOrEmpty(snapInfo.LicensePlate))
{
Console.WriteLine($"抓拍到车牌{snapInfo.LicensePlate}!");
OnSnapLicense(GetCarInfoBySnapInfo(snapInfo));
}
else
{
Console.WriteLine("当前没有抓拍到车牌!");
Console.WriteLine("--------------------------------------");
}
}
});
thread.IsBackground = true;
thread.Start();
}
public CarInfo GetCarInfoBySnapInfo(SnapInfo snapInfo)
{
//抓拍到车牌后,这里直接赋值,相当于模拟通过接口车牌查询了该车的进场数据
CarInfo carInfo = new CarInfo()
{
StartTime = DateTime.Parse("2022-09-09 12:00:00"),
EndTime = DateTime.Now,
LicensePlate = snapInfo.LicensePlate,
};
return carInfo;
}
public void OnSnapLicense(CarInfo carInfo)
{
OnSnapLicenseEvent?.Invoke(this, carInfo);
}
}
public class SnapInfo
{
//车牌,拍到车牌表示车来了,没拍到车牌,表示没有车
public string LicensePlate { get; set; }
}
public class CarInfo : EventArgs
{
//停车的开始时间
public DateTime StartTime { get; set; }
//停车的结束时间
public DateTime EndTime { get; set; }
//车牌号
public string LicensePlate { get; set; }
}
//收费员(负责收费)
public class Charger
{
//收费
public void Charge(object sender, CarInfo carInfo)
{
Console.WriteLine($"收费员:对{carInfo.LicensePlate}完成了收费");
}
}
//闸机(负责开关)
public class Gate
{
public void OpenGate(object sender, CarInfo carInfo)
{
Console.WriteLine($"闸机:放行{carInfo.LicensePlate}");
Console.WriteLine("--------------------------------------");
}
}
测试结果
总结
以上就是今天要讲的内容,事件为我们的编程提供了很大的方便,熟练的使用事件也是每个C#开发人员必备的知识,希望这些讲解能够帮助你加深对事件的理解。