目录
在 C# 中,事件(Event)是一种特殊的委托,基于委托的并为委托提供一个发布/订阅的机制,可以说事件是一个具有特殊签名的委托。
事件的特点
- 只能在声明事件的类内部触发(保证封装性)
- 订阅者只能通过
+=
和-=
来订阅和取消订阅 - 支持多播(多个订阅者可以订阅同一个事件)
- 事件是委托的安全包装,防止外部直接调用或赋值
事件的基本用法
event 是在委托上面加上关键字 加上之后在使用委托的时候会增加一些限制
这里以大学懒人舍友和怨种舍友为例,只有怨种下楼了,才可以给懒人室友带饭、拿快递:
怨种舍友类ToolMan
internal class ToolMan
{
public string Name { get; set; }
//构造函数
public ToolMan(string name)
{
Name = name;
}
//定义一个委托
public delegate void DownDelgate();
//event 是在委托上面加上关键字 加上之后在使用委托的时候会增加一些限制
public event DownDelgate downDelgate = null;
//创建了一个有参数(string参数类型)的事件 action
public event Action<string> action;
//调用事件的函数
public void Down()
{
Console.WriteLine($"{Name}下楼了");
downDelgate();
action("怨种回来了");
}
//打印字符串显示函数
public void Show(string str)
{
Console.WriteLine(str);
}
}
懒人舍友类LazyMan
internal class LazyMan
{
public string Name { get; set; }
public LazyMan(string name)
{
this.Name = name;
}
public void TakeFood()
{
Console.WriteLine($"{Name}买饭");
}
public void GetFoot()
{
Console.WriteLine($"{Name}拿快递");
}
}
主函数调用
先声明一些懒人和调用懒人类中所做的事的函数订阅在事件里:
static void Main(string[] args)
{
ToolMan T = new ToolMan("怨种");
LazyMan L1 = new LazyMan("小明");
LazyMan L2 = new LazyMan("小张");
LazyMan L3 = new LazyMan("小李");
//+=订阅
T.downDelgate += L1.TakeFood;
T.downDelgate += L2.TakeFood;
T.downDelgate += L3.TakeFood;
//-=取消订阅订阅
T.downDelgate -= L3.TakeFood;
Console.WriteLine("怨种出发了");
怨种舍友还没有下楼,委托还不能发生,所以使用事件声明委托这里将不能提前执行会报错
//T.downDelgate();
//T.action("12300");直接订阅会报错
因此:在ToolMan类中的Down()函数调用写入这两个事件,当下楼函数执行时,事件也会发生。
T.Down();
}
这里 怨种工具人还没下楼 委托的事件也发生了 这不合理,这样写T.downDelgate(); 直接调用委托 会发现 工具人还没有下楼就执行了操作 事件还没有执行
怎么解决? 使用事件
报错说明: 事件的访问权限与使用场景不匹配
错误本质:C# 中,事件(
event
)是特殊的委托封装,为保证封装性:
- 在定义事件的类(
ToolMan
)外部,事件只能通过+=
(订阅)、-=
(取消订阅)操作,不能直接赋值(=
)、调用或作为委托变量传递。- 代码里的
ToolMan.downDelgate
、ToolMan.action
是事件,却在类外部被当作普通委托用了(比如直接调用、赋值等),违反了事件的访问规则,所以编译器报错。解决方案:这些事件都是怨种下楼之后才能操作的,且这些事件的调用只能在类本身访问使用,所以这里在ToolMan类中的Down()函数调用写入这两个事件,当下楼函数执行时,事件也会发生。
事件练习:
示例代码:
含有事件的类Class1,此处写一个函数Boilwater来判断水温,当水温达到95°,事件发生
internal class Class1 { //创建了一个事件 public event Action<int> action; public void Boilwater() { for (int i = 0; i <= 100; i++) { if (i>=95) { action(i); } } } }
报警器类Class3
internal class Class3 { public void MakeAi(int paras) { Console.WriteLine($"嘀嘀嘀:水已经{paras}"); } }
液晶屏类Class2
internal class Class2 { public void ShowMsg(int param) { Console.WriteLine($"水温到了 当前水温{param}"); } }
主函数调用
static void Main(string[] args) { Class1 c1= new Class1(); Class2 c2= new Class2(); Class3 c3= new Class3(); c1.action += c2.ShowMsg; c1.action += c3.MakeAi; c1.Boilwater(); }
示例代码:
定义一个类Mom包含 KaiFan事件 和 cook函数
internal class Mom { public event Action<string> KaiFan; public void cook() { Console.WriteLine("做饭"); KaiFan("开饭"); } }
定义一个Dad类 包含虚方法eat
internal class Dad { public virtual void eat(string a) { Console.WriteLine(a+" 爸爸吃饭"); } }
定义一个Son类继承父类Dad 包含重写eat方法
internal class Son: Dad//继承Dad父类方法 { public override void eat(string a) { Console.WriteLine(a + " 儿子吃饭"); } }
主函数订阅
static void Main(string[] args) { Mom mom = new Mom(); Dad dad = new Dad(); Son son = new Son(); //订阅 mom.KaiFan += Show; mom.KaiFan += dad.eat; mom.KaiFan += son.eat; //事件发生做饭 mom.cook(); } public static void Show(string str) { Console.WriteLine(str); }
补充:Action 委托函数
在 C# 中,Action
是一个预定义的泛型委托,位于 System
命名空间下,用于表示没有返回值的方法。它简化了委托的定义,无需手动声明委托类型,直接使用即可。
1. Action
的基本形式
Action
有两种常见形式:
无参数的
Action
:表示没有参数且无返回值的方法。public delegate void Action();
带参数的
Action<T>
:泛型版本,支持 1~16 个参数(例如Action<T1>
、Action<T1, T2>
等),同样没有返回值。// 示例:带两个参数的 Action public delegate void Action<T1, T2>(T1 arg1, T2 arg2);