C#事件的原理和用法整理
事件是什么?
事件是能够让对象或类具备通知能力的成员。是一种类型成员(没有产品就没有发布,没有公司就没有上市)。它是用于对象和类之间的动作协调和信息传递的
事件模型如下:
“发生->响应”
5个动作——
- 我有一个事件
- 我关心这个事件
- 这个事件发生了
- 关心这个事件的人被通知到
- 被通知到的人根据拿到的事件信息对事件进行响应
5个部分——
- 事件的拥有者(event source 对象)
- 事件成员(event 成员) 事件是被动触发的,它需要通过事件拥有者进行触发
- 事件的响应者(eventsubscriber 对象)当事件发生时,哪些对象去响应
- 事件处理器(event handler 成员)响应者的方法成员
- 事件订阅 把事件处理器和事件关联在一起,本质上是一种以委托类型为基础的约定
从响应者角度来看,他们除了收到事件通知,还接收到经由事件发送过来的与事件本身相关的信息,称为“事件参数”(Event Args)
被通知的人根据事件参数对事件进行响应(处理事件),(处理事件)所做的事情,称为事件处理器(Event Handler)
因此我们可以说 事件的功能就是=通知+可选的事件模型
我们可以通过一个简单的代码例子来理解5个组成部分的关系。
查看代码
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer();//timer是事件拥有者
timer.Interval = 1000;
Boy boy = new Boy();//boy是事件响应者
timer.Elapsed += boy.Action; //Elapsed是事件 +=是订阅
timer.Start();
Console.ReadKey();
}
}
public class Boy
{
//事件处理器
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("hello");
}
}
我们用Boy作为事件响应者 用其事件处理器Action来订阅事件拥有者Timer类对象中自带的Elapsed事件
此外对于事件我们的使用情况通常有三种:
1、事件拥有者和响应者是两个相互独立的类,响应者类的处理器订阅拥有者类的事件
查看代码
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Form form = new Form();
Controller controller = new Controller(form);
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
this.form.Click += this.Clicked;
}
}
private void Clicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
}
2、事件拥有者和响应者属于同一个类,这个类自己订阅自己的事件
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();
myForm.Click += myForm.FormClicked;
myForm.ShowDialog();
}
}
class MyForm : Form
{
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
3、事件拥有者作为响应者的一个成员,或者是事件响应者作为拥有者的一个成员
查看代码
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
MyForm myForm = new MyForm();
myForm.ShowDialog();
}
}
class MyForm : Form
{
private TextBox TextBox;
private Button button;
public MyForm()
{
this.TextBox = new TextBox();
this.button = new Button();
this.Controls.Add(this.button);
this.Controls.Add(this.TextBox);
this.button.Click += this.ButtonClicked;
this.button.Text = "Click";
this.button.Top = 50;
}
private void ButtonClicked(object sender, EventArgs e)
{
this.TextBox.Text = "Hello World!!!!!!!!!!!!";
}
}
}
上面我们用的是C#帮我们写好的事件
那我们如何来自定义一个事件呢?C#自带事件的内部实现是什么样的呢?
还记得我们之前说的,事件是基于委托的吗。这个特性会在我们声明事件时进行一个体现
委托有以下作用:
类型兼容:委托对事件做了一个约束,规定了事件和事件处理器应当匹配
存储方法的引用:委托类型的实例将这组匹配信息保存了下来
因此我们说事件需要搭配一个委托。它通常是声明在事件拥有者类外的
// 步骤1,声明delegate 如果这个委托是为了约束某个事件而声明的委托 那么通常就将其命名为“事件名+EventHandler”
public delegate void MyEventHandler(object sender, System.EventArgs e);
我们可以给个更加具体的例子,比如我们声明一个点餐的委托,我们想让Customer顾客作为事件拥有者,还要传递价格、大小等事件参数。那我们就可以这样声明
public delegate void OrderEventHandler(Customer customer, EventArgs e);
之后我们就可以声明我们的事件了。(记住事件是属于事件拥有者的,它需要写在事件拥有者类的内部)
有完整声明和简略声明两种形式
完整声明:
private OrderEventHandler OrderEventHandler;//声明委托类型字段。用于存储和引用事件处理器
public event OrderEventHandler Order//声明事件Order。用OrderEventHandler来约束事件
{
add//事件处理器的添加器
{
this.OrderEventHandler += value;
}
remove//事件处理器的移除器
{
this.OrderEventHandler -= value;
}
}
简略声明:
private event MyEventHandler myevent;
多种事件订阅格式
this.button3.Click += MyButton_Click;//第一种挂接事件方法
this.button3.Click += new EventHandler(this.MyButton_Click);//第二种
this.button3.Click += delegate (object sender, EventArgs e)//第三种 匿名方法
{
this.MyTextBox.Text = "haha";
};
this.button3.Click += (sender,e) => //第四种 lambda表达式
{
this.MyTextBox.Text = "hoho";
};
事件与委托的关系
事件真的是“以特殊方式声明的委托字段/实例吗”?
不是!只是声明的时候“看起来像”(对比委托字段的和事件的简化声明,field-like)
事件声明的时候使用了委托类型,简化声明造成事件看上去像一个委托的字段(实例),而event关键字则更像是一个修饰符——这就是错觉的来源之一
订阅事件的时候+=操作符后面可以是一个委托实例,这与委托实例的赋值方法语法相同,这也让事件看起来像是一个委托字段——这是错觉的又一来源
重申:事件的本质是加装在委托字段上的一个“蒙板”(mask),是个起掩蔽作用的包装器,这个用于阻挡非法操作的“蒙板”绝不是委托字段本身
为什么要使用委托类型来声明事件?
站在source的角度来看,是为了表明source能对外传递哪些消息
站在subscriber的角度来看,它是一种约定,是为了约束能够使用什么样签名的方法来处理(响应)事件
委托类型的实例将用于存储(引用)事件处理器
对比事件和属性
属性不是字段——很多时候属性是字段的包装器,这个包装器用来保护字段不被滥用
事件不是委托字段——它是委托字段的包装器,这个包装器用于保护委托字段不被滥用
包装器永远都不可能是被包装的东西
下面给出一个参考实例
查看代码
namespace EventExample
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer();
Waiter waiter = new Waiter();
customer.Order += waiter.Action;//挂接事件。waiter的Action订阅着customer的Order
customer.Action();
customer.PayTheBill();
}
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);//声明一个委托类型,专门用来声明事件,约束事件处理器
public class OrderEventArgs : EventArgs//声明用来传递消息的类,派生自EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public class Customer //事件发起者
{
public event OrderEventHandler Order;//声明事件Order。用OrderEventHandler来约束事件
public double Bill { get; set; }
public void Walkin()
{
Console.WriteLine("Walk in the restaurant");
}
public void Sitdown()
{
Console.WriteLine("Sit down");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think......");
Thread.Sleep(1000);
}
if (this.Order != null)//等于空说明没有人订阅这个事件,会报异常
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
Walkin();
Sitdown();
Think();
}
public void PayTheBill()
{
Console.WriteLine("I Will pay ${0}.",this.Bill);
}
}
public class Waiter //事件的响应者Waiter
{
public void Action(Customer customer, OrderEventArgs e)//事件处理器
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库