c#笔记之事件
写在前面
1.事件和委托的关系
事件是基于委托的,可以理解为事件是选择谁可以调用这个委托是对委托的一层保护;因为谁调用事件是有条件的;而且用了事件这个委托的调用只能在事件拥有者的内部去执行,外部没办法执行;
为什么说事件是基于委托的:1.事件需要用委托类型来做一个约束,这个约束既规定了事件能发送什么消息给响应者,也规定了响应者能收到什么样的事件消息,这就决定了事件响应者的事件处理器必须能够给这个约束匹配上,才能够订阅这个事件。
2.当事件的响应者给事件的拥有者提供了能够匹配这个事件的事件处理器之后,得找个地方把事件处理器保存或者记录下来,能够记录或者引用方法的任务也只有委托类型的实例能够做到。
一、事件定义
事件就是能够发生的事;让对象和类有通知别的对象的能力
二、事件模型的5
比如闹钟响了我就起床,1.闹钟;2.响;3.我;4.起床还有一个隐藏的5.我订阅了这个闹钟的事件,所以只有这个闹钟响了我才会有反应
2.1事件的拥有者
闹钟
2.2事件
响
2.3事件处理者
我
2.4事件处理器
起床
2.5事件订阅
我只关注我的这个闹钟;别的闹钟响了我不会起床
三、事件声明使用
例子是一个点菜事件,按照上面的5要素来说:顾客就是点菜事件的拥有者;点菜就是事件,服务员就是事件的处理者订阅了顾客点菜这个事件;事件处理器就是收集顾客点的菜给厨房并且上菜算账;
3.1完整声明
完整声明不常用;声明时就按照五要素声明就可以
下面是一些事件的约定俗称的命名方式看到就可以知道这是事件相关的:
1.事件委托一般以xxxHandler命名
2.事件参数一般以xxxEventArgs命名并且继承自EventArgs
3.1.1声明事件委托
因为事件的基于委托的使用先声明委托;注意委托是特殊的类要声明在命名空间内;与类平齐;
这里的事件委托声明的时候有两个参数一个顾客类还有一个是菜类;所以这个事件的响应方法也必须要有这两个参数;并且在创建事件参数类的时候要继承自EventArgs类;:EventArgs是微软自带的一个事件参数类;这里之所以两个参数除了是需要两个参数之外还有一个原因是微软自带的简单事件声明固定要两个参数,在初学的时候方便记忆;在自定义事件委托的时候几个参数都可以;
如果要创建的事件不需要事件参数可以直接 public delegate void OrderHandler();
namespace ConsoleApp16
{ internal class Program//程序运行的地方{static void Main(string[] args){}}public class OrderEventArgs :EventArgs//事件参数;点的菜的名字
{public string _Name { get; set;}
}public delegate void OrderHandler(Customer senrder,OrderEventArgs orderEventArgs);}
3.1.2事件的发布者(拥有者)
创建一个顾客类;一般来说事件的触发也是由事件的拥有者这个类里的方法如下面的Action方法来触发点菜事件
public class Customer{public event OrderHandler Order;//声明事件public void Action()//事件的触发{Console.WriteLine("开始点菜");OrderEventArgs o = new OrderEventArgs();o._Name = "满汉全席";Order?.Invoke(this,o);//执行事件}
}
3.1.3事件的订阅者(处理者)
下面的Action就是事件的处理器 ,Action方法的参数和事件委托的方法一样所以这个方法才可以订阅这个委托
public class Waiter{public void Action(Customer senrder, OrderEventArgs orderEventArgs){Console.WriteLine(orderEventArgs._Name+"点菜完毕");}}
3.1.4事件的订阅
因为这里是在c#控制台的情况下的所以事件的订阅是在main方法里
namespace ConsoleApp16
{ internal class Program//程序运行的地方{static void Main(string[] args){Console.WriteLine("餐馆开始运作...";Customer c = new Customer();Waiter w = new Waiter();c.Order += w.Action;事件的订阅//////c.Action();开始点菜}}
}
完整代码
namespace ConsoleApp16
{ internal class Program//程序运行的地方{static void Main(string[] args){Console.WriteLine("餐馆开始运作...";Customer c = new Customer();c.Customername="小明";Waiter w = new Waiter();c.Order += w.Action;事件的订阅//////c.Action();开始点菜}}public class OrderEventArgs :EventArgs//事件参数;点的菜的名字{public string _Name { get; set;}}public delegate void OrderHandler(Customer senrder,OrderEventArgs orderEventArgs);//////顾客public class Customer{public event OrderHandler Order;//声明事件public string Customername;public void Action()//事件的触发{Console.WriteLine("开始点菜");OrderEventArgs o = new OrderEventArgs();o._Name = "满汉全席";Order?.Invoke(this,o);//执行事件}}//////////////服务员public class Waiter{public void Action(Customer senrder, OrderEventArgs orderEventArgs){Console.WriteLine(senrder.Customername+"点了"orderEventArgs._Name+"点菜完毕");}}}
3.2便捷声明
在c#里可以直接使用系统准备好的事件委托EventHandler和EventHandler<>前者适由于无事件参数的情况后者是有参数;这样就不用重新声明事件委托;
为什么EventHandler和EventHandler<>要两个参数
- 统一的参数结构让开发者在使用不同类库的事件时,能遵循一致的编程习惯(例如所有事件处理程序都能通过
sender
获取源头,通过e
获取数据)。- 框架中的所有内置事件(如
Button.Click
、Form.Load
)均遵循此模式,自定义事件使用相同模式可保持生态一致性。- 早期 .NET 设计时就确定了这种双参数模式,后续框架(如 WPF、ASP.NET)均延续了这一设计。如果允许无参数或单参数事件,会导致不同库的事件模型混乱,增加开发者的学习和维护成本。
EventHandler和EventHandler<>两个参数的作用
1.
sender
参数:明确事件源第一个参数
object? sender
用于传递触发事件的对象实例,这是为了让事件处理程序知道 “谁触发了事件”。
- 在复杂场景中,一个事件处理程序可能同时订阅多个对象的事件(例如多个按钮共用一个点击事件处理方法),通过
sender
可以区分事件来源,执行针对性逻辑。- 示例:多个
Button
控件绑定同一个OnClick
方法,通过sender
可判断具体是哪个按钮被点击:void OnButtonClick(object? sender, EventArgs e) {if (sender is Button btn){Console.WriteLine($"按钮 {btn.Name} 被点击");} }
2.
e
参数:传递事件数据(可扩展)第二个参数(
EventArgs e
或TEventArgs e
)用于传递事件相关的数据,这是事件模型灵活性的核心:
- 对于简单事件(如 “完成通知”),可使用
EventArgs.Empty
表示无额外数据。- 对于需要传递信息的事件(如 “订单完成” 需传递订单 ID、金额),可通过自定义
TEventArgs
(继承自EventArgs
)扩展数据,而无需修改事件委托的签名。- 这种设计遵循开闭原则:事件发布者和订阅者无需因数据变化而修改核心逻辑,只需扩展
EventArgs
子类即可。
3.2.1EventHandler
虽然EventHandler事件委托是系统准备的可以直接用;但是EventHandler底层委托在系统里的格式是这个;
public delegate void EventHandler(object sender, EventArgs e);
3.2.1.1顾客类要变
事件委托因为不用重新声明所以要换成系统准备EventHandler委托;执行事件invoke的参数因为事件参数是空的但是又因为规定有参数EventArgs所以使用EventArgs.Empty;
//////顾客public class Customer{public event EventHandler Order;******新声明事件public void Action()//事件的触发{Console.WriteLine("开始点菜");Order?.Invoke(this,EventArgs.Empty);*******新执行事件}}
3.2.1.2服务员类
因为 EventHandler事件委托在系统自动生成的格式是public delegate void EventHandler(object sender, EventArgs e);使用订阅事件的参数也要一致;
//////////////服务员public class Waiter{public void Action(object senrder, EventArgs orde)*****新{Console.WriteLine("点菜完毕");}}
完整代码
namespace ConsoleApp16
{ internal class Program//程序运行的地方{static void Main(string[] args){Console.WriteLine("餐馆开始运作...";Customer c = new Customer();Waiter w = new Waiter();c.Order += w.Action;事件的订阅//////c.Action();开始点菜}}//////顾客public class Customer{public event EventHandler Order;******新声明事件public void Action()//事件的触发{Console.WriteLine("开始点菜");Order?.Invoke(this,EventArgs.Empty);*******新执行事件}}//////////////服务员public class Waiter{public void Action(object senrder, EventArgs orde)*****新{Console.WriteLine("点菜完毕");}}}
3.2.2EventHandler<>
底层自动声明是下面这个格式
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
3.2.2.1顾客类
事件委托换成带事件参数的EventHandler<>;事件执行那里因为在声明事件的时候EventHandler<OrderEventArgs> 并且OrderEventArgs是继承自EventArgs的所以invoke参数可以直接使用实例o;
//////顾客public class Customer{public event EventHandler<OrderEventArgs> Order;******新声明事件public void Action()//事件的触发{Console.WriteLine("开始点菜");OrderEventArgs o = new OrderEventArgs();o._Name = "满汉全席";Order?.Invoke(this,o);*******新执行事件}}
3.2.2.2服务员
同样的方法的参数要保持一致,第二个参数可以直接是OrderEventArgs类型;
//////////////服务员public class Waiter{public void Action(object senrder, OrderEventArgs orderEventArgs)*****新{Console.WriteLine(orderEventArgs._Name+"点菜完毕");}}
完整代码
namespace ConsoleApp16
{ internal class Program//程序运行的地方{static void Main(string[] args){Console.WriteLine("餐馆开始运作...";Customer c = new Customer();Waiter w = new Waiter();c.Order += w.Action;事件的订阅//////c.Action();开始点菜}}public class OrderEventArgs :EventArgs//事件参数;点的菜的名字{public string _Name { get; set;}}//////顾客public class Customer{public event EventHandler<OrderEventArgs> Order;******新声明事件public void Action()//事件的触发{Console.WriteLine("开始点菜");OrderEventArgs o = new OrderEventArgs();o._Name = "满汉全席";Order?.Invoke(this,o);*******新执行事件}}//////////////服务员public class Waiter{public void Action(object senrder, OrderEventArgs orderEventArgs)*****新{Console.WriteLine(orderEventArgs._Name+"点菜完毕");}}}
特别情况如果两个参数
比如和自定义委托事件一样我需要知道是谁点的菜;因为第一个参数object senrder本义就是用于传递触发事件的对象实例,也就是知道是谁;比如和最开始的例子一样顾客类有个名字属性这时候的服务员类可以变成这样;as就是可以把一个类转变成另外一个类型因为object是一切类的基类所以可以转成顾客类;
//////////////服务员public class Waiter{public void Action(object senrder, OrderEventArgs orderEventArgs)*****新{Customer name = senrder as Customer;Console.WriteLine(name.CustomerName+"点了"orderEventArgs._Name+"点菜完毕");}}
学习时间:
25.10.22