事件 (Event)
- 能够发生的什么事情
角色
- 使对象或类具备通知能力的成员
- 事件是一种使对象或类能够提供通知的成员
- 对象O拥有一个事件E == 当事件E发生的时候,O有能力通知别的对象
原理
- 事件模型中的两个5:
- 发生–>响应中的5个部分:闹钟响了你起床、孩子饿了你做饭…这里隐含着"订阅"关系
- 发生–>响应中的5个动作:(1)我有一个事件。(2)一个人或者一群人关心我的这个事件。(3)我的这个事件发生了。(4)关心这个事件的人会被依次通知到。(5)被通知到的人根据拿到的事件信息(又称“事件数据”、“事件参数”、“通知”)对事件进行响应(又称“处理事件”)。
事件的应用
- 5个组成部分:
1、事件的拥有者(event source,对象)
2、事件成员(event,成员)
3、事件的响应者(event subscri,对象)
4、事件的处理器(event handler,成员)---- 本质上是一个回调方法
5、事件订阅 ---- 把事件处理器与事件关联在一起,本质上是一种以委托类型为基础的“约定”
ps:
- 事件处理器是方法成员
- 挂接事件处理器的时候,可以使用委托实例,也可以直接使用方法名,这是个“语法糖”
- 事件处理器对事件的订阅不是随意的,匹配与否由声明事件时所使用的委托类型来检测
- 事件可以同步调用也可以异步调用
一个事件有2个处理器
public class Program
{
static void Main(string[] args)
{
//事件的拥有者 Timer
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = 1000;
Boy boy = new Boy();
Girl girl = new Girl();
//事件的成员是:Elapsed +=为订阅(左边是事件,右边是事件处理器)
timer.Elapsed += Boy.Action;
timer.Elapsed += Girl.Action;
timer.Start();
Console.ReadLine();
}
}
//事件的响应者
class Boy
{
internal static void Action(object? sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump!");
}
}
class Girl
{
internal static void Action(object? sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing!");
}
}
internal class Program
{
static void Main(string[] args)
{
//事件的拥有者form
Form form = new Form();
//事件的响应者 controller
Controller controller = new Controller(form);
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
//事件是form的click
this.form.Click += this.FormClicked;
}
}
//事件处理器
private void FormClicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
internal class Program
{
static void Main(string[] args)
{
//拥有者form 响应者form
MyForm form = new MyForm();
//事件Click
//事件的订阅 +=
form.Click += form.FormClicked;
form.ShowDialog();
}
}
//既保留原本的方法,又有新的方法(扩展)----派生(继承)
class MyForm : Form
{
//事件的处理器
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
internal class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();
form.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 = "Say Hello";
this.button.Top = 50;
}
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "Hello World!!!!!!!!!";
}
}
事件
- 是委托的多播功能进行封装后的工具类型
理解:如果没有“事件”,那么aoe伤害怎么表示
class Enemy
{
private int blood = 100;
public void MinusBlood(int attack)
{
blood -= 10;
}
}
class Player
{
public void DoAOE(Enemy[] enemies)
{
foreach(Enemy e in enemies)
{
e.MinusBlood(10);
}
}
}
这样的后果会导致,如果Enemy中的MinusBlood更改了名字,Player的代码也需要随之更改
耦合:A改变了,B被迫改变—B耦合与A(B依赖于A)
如何解耦合呢?
//
//需求:
//1 Player Class:能够释放AOE范围攻击技能,让波及到的敌人统一减血10滴
//2 Enemy Class:能够对外提供减血的方法函数,供外界调用
//
internal class BreakfastMaker
{
static void Main(string[] args)
{
Player player = new Player();
Enemy e0 = new Enemy();
Enemy e1 = new Enemy();
Enemy e2 = new Enemy();
NPC npc = new NPC();
player.OnAttack += e0.MinusBlood;
player.OnAttack += e1.MinusBlood;
player.OnAttack += e2.MinusBlood;
player.OnAttack += npc.BeAttacked;
player.DoAOE();
Console.ReadLine();
}
}
class Enemy
{
private int blood = 100;
public void MinusBlood(int attack)
{
Console.WriteLine("好疼!我是enemy");
blood -= attack;
}
}
class NPC
{
private int blood = 100;
public void BeAttacked(int attack)
{
Console.WriteLine("好疼!我是npc");
blood -= attack;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
public delegate void OnAttackDelegate(int attack);
public OnAttackDelegate OnAttack = null;
//现在整个player里面都没有enemy的出现
public void DoAOE()
{
//里面是有委托方法的
if(OnAttack != null)
{
OnAttack(10);
}
}
}
策划不断添加新的功能,导致不断添加新的参数,对此要做出一定的改变
从而引出事件
//
//需求:
//1 Player Class:能够释放AOE范围攻击技能,让波及到的敌人统一减血10滴
//2 Enemy Class:能够对外提供减血的方法函数,供外界调用
//
//问题:委托方法,需要携带的参数太多了,参数还会经常变动
//方法:将所有的参数都包含在一个class里面
//
class EventArgs
{
public int attack = 0;
public bool poisoned = false;
}
internal class BreakfastMaker
{
static void Main(string[] args)
{
Player player = new Player();
Enemy e0 = new Enemy();
Enemy e1 = new Enemy();
Enemy e2 = new Enemy();
NPC npc = new NPC();
player.OnAttack += e0.MinusBlood;
player.OnAttack += e1.MinusBlood;
player.OnAttack += e2.MinusBlood;
player.OnAttack += npc.BeAttacked;
player.DoAOE();
Console.ReadLine();
}
}
class Enemy
{
private int blood = 100;
//poisoned --- 是否中毒
public void MinusBlood(object o,EventArgs args)
{
Console.WriteLine("好疼!我是enemy");
blood -= args.attack;
//判断是否中毒
if(args.poisoned)
{
Console.WriteLine("Emeny中毒了");
}
//(敌人)发动背刺技能
Player p = (Player)o;
p.Shout();
}
}
class NPC
{
private int blood = 100;
public void BeAttacked(object o, EventArgs args)
{
Console.WriteLine("好疼!我是npc");
blood -= args.attack;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
public delegate void OnAttackDelegate(object o,EventArgs args);
public OnAttackDelegate OnAttack = null;
//现在整个player里面都没有enemy的出现
public void DoAOE()
{
//里面是有委托方法的
if(OnAttack != null)
{
EventArgs args = new EventArgs();
args.attack = 10;
args.poisoned = true;
OnAttack(this,args);
}
}
public void Shout()
{
Console.WriteLine("玩家很痛");
}
}
最终完全代码
//
//需求:
//1 Player Class:能够释放AOE范围攻击技能,让波及到的敌人统一减血10滴
//2 Enemy Class:能够对外提供减血的方法函数,供外界调用
//
//问题:委托方法,需要携带的参数太多了,参数还会经常变动
//方法:将所有的参数都包含在一个class里面
//
class EventArgs
{
public int attack = 0;
public bool poisoned = false;
public bool headache = false;
}
internal class BreakfastMaker
{
static void Main(string[] args)
{
Player player = new Player();
Enemy e0 = new Enemy();
Enemy e1 = new Enemy();
Enemy e2 = new Enemy();
NPC npc = new NPC();
player.OnAttack += e0.MinusBlood;
player.OnAttack += e1.MinusBlood;
player.OnAttack += e2.MinusBlood;
player.OnAttack += npc.BeAttacked;
player.DoAOE();
Console.ReadLine();
}
}
class Enemy
{
private int blood = 100;
//poisoned --- 是否中毒
public void MinusBlood(object o,EventArgs args)
{
Console.WriteLine("好疼!我是enemy");
blood -= args.attack;
//判断是否中毒
if(args.poisoned)
{
Console.WriteLine("Emeny中毒了");
}
//判断是否眩晕
if(args.headache)
{
Console.WriteLine("Emeny眩晕了");
}
//(敌人)发动背刺技能
Player p = (Player)o;
p.Shout();
}
}
class NPC
{
private int blood = 100;
public void BeAttacked(object o, EventArgs args)
{
Console.WriteLine("好疼!我是npc");
blood -= args.attack;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
public delegate void OnAttackDelegate(object o,EventArgs args);
public OnAttackDelegate OnAttack = null;
//现在整个player里面都没有enemy的出现
public void DoAOE()
{
//里面是有委托方法的
if(OnAttack != null)
{
EventArgs args = new EventArgs();
args.attack = 10;
args.poisoned = true;
args.headache = true;
OnAttack(this,args);
}
}
public void Shout()
{
Console.WriteLine("玩家很痛");
}
}
C#里面已经写好了事件专用委托定义
//这段是自己写的
class Player
{
public delegate void OnAttackDelegate(object o,EventArgs e);
public OnAttackDelegate OnAttack = null;
}
//这是微软离写好的事件委托定义
public delegate void EventHandler(object? sender,EventArgs e);
//偷懒后写法
class Player
{
public EventHandler OnAttack = null;
}
进一步思考
事件对应的委托,不应该被类外界调用,只能由某个操作触发
Player player = new Player();
player.OnAttack();
//因为OnAttack里面还有其他的方法,如果直接调用的话,可能跑着跑着就掉血了(就是调用其他不合适的方法)
Player player = new Player();
player.DoAOE();
//调用指定的方法,这个方法里再调用委托才符合逻辑
事件对应的委托,不应该被类外界直接赋值,只能够通过+、-增减委托方法
Player player = new Player()
player.OnAttack += 某方法
event关键字
- 加event关键字修饰的委托,只能够定义在某个类内
- 加event关键字修饰的委托,只能够被当前类内方法触发执行,类外不可触发执行
- 加event关键字修饰的委托,只能通过+、-增减委托,不可赋值
//使用event后,这个委托升级了!变成了事件委托
class Player
{
public event EventHandler OnAttack = null;
}
class Player
{
//定义Player内部会被触发的事件委托
public event EventHandler OnAttack = null;
public void DoAOE()
{
if(OnAttack != null)
{
OnAttack(this, EventArgs.Empty);
}
}
}
class Enemy
{
public void AttackMe(object sender, EventArgs e)
{
Console.WriteLine("我被攻击了");
}
}
internal class Program
{
static void Main(string[] args)
{
Player player = new Player();
Enemy e = new Enemy();
//调用方法1
player.OnAttack += e.AttackMe;
player.DoAOE();
//调用方法2 加了event就不能用了
//player.OnAttack(new object(),EventArgs.Empty);
//调用方法3 直接赋值 加了event就不能使用
//EventHandler handler = new EventHandler(e.AttackMe);
//player.OnAttack = handler;
//handler(new object(), EventArgs.Empty);
Console.ReadLine();
}
}
事件参数EventArgs
- 事件被触发的时候,可以传送自定义的事件参数
步骤
- 定义自己事件参数包class
- 将EventHandler转化成传输咱们自己的参数包类的委托类型
- 响应方法中,将事件参数包类型替换成我们自己的参数包类型
// 1、定义事件类
class MyEventArgs
{
public string text = "";
public int number = 0;
public bool flag = false;
}
// 2、定义事件源
class Player
{
//使用泛型
public event EventHandler<MyEventArgs> AttackEvent;
......(一堆属性)
}
//定义事件处理方法
static public void Test(object? sender,MyEventArgs e)
{
......
}
事件的正确用法
class MyArgs
{
public int attack = 0;
public bool poisoned = false;
public bool headache = false;
}
class Player
{
//public delegate void EventHandler(object sender,MyArgs e)
public event EventHandler<MyArgs> OnAttack;
public void DoAOE()
{
if(OnAttack != null)
{
MyArgs args = new MyArgs();
args.attack = 10;
args.poisoned = true;
args.headache = true;
OnAttack(this, args);
}
}
}
class Enemy
{
public void AttackMe(object sender,MyArgs args)
{
Console.WriteLine("敌人被攻击了");
Console.WriteLine("攻击力:"+args.attack);
Console.WriteLine("是否中毒:"+args.attack);
Console.WriteLine("是否晕眩:"+args.headache);
}
}
internal class Program
{
static void Main(string[] args)
{
Player p = new Player();
Enemy e = new Enemy();
p.OnAttack += e.AttackMe;
p.DoAOE();
}
}
练习:
请编写键盘输入管理类:InputManager,并对外暴露OnKeyInput事件,表示用户输入了一个字符
要求:使用自定义事件,将键盘输入的字符给订阅者
//
//编写一个InputManager的Class,用来接收用户输入的一个字符,之后调用OnInput事件,本事件会
//传导给监听者两个东西(object sender,InputArgs args),args里携带着用户输入的字符
//
namespace ConsoleApp1
{
class InputArgs
{
public char input;
}
class InputManager
{
//public delegate void EventHandler(object sender,InputArgs e)
public event EventHandler<InputArgs> OnInput;
//等待用户输入一个字符
public void WaitForInput()
{
while(true)
{
// 1、读取用户输入的一个字符
char input = Convert.ToChar(Console.Read());
// 2、调用OnInput事件,将这个字符传导到监听方法/监听者
if(OnInput != null)
{
InputArgs args = new InputArgs();
args.input = input;
OnInput(this, args);
}
}
}
}
internal class Program
{
static public void OnKeyInput(object sender,InputArgs args)
{
Console.WriteLine("收到了OnInput事件,拿到了字符:"+args.input);
}
static void Main(string[] args)
{
InputManager im = new InputManager();
im.OnInput += OnKeyInput;
im.WaitForInput();
}
}
}
事件练习2
请编写人类喂食宠物的过程:
- 事件源:人类,人可以发出喂食的事件
- 事件参数:喂什么事物
- 事件订阅者:狗/猫/熊猫
- 事件处理方法:动物们的OnFeed方法,判断当前事物是否爱吃
//
// 模拟主人喂食宠物的过程,三个宠物:狗、猫、熊猫
// 事件源:主人
// 监听者:狗、猫、熊猫 Eat
// 事件参数包:string food
//
namespace ConsoleApp1
{
class FeedArgs
{
public string food = "";
}
class Master
{
public event EventHandler<FeedArgs> OnFeed;
public void FeedAnimals(string food)
{
if(OnFeed != null)
{
FeedArgs args = new FeedArgs();
args.food = food;
OnFeed(this, args);
}
}
}
class Dog
{
//监听,所以参数列表需要一致
public void Eat(object sender,FeedArgs args)
{
if(args.food != "肉肉")
{
Console.WriteLine("狗狗:不爱吃");
}
else
{
Console.WriteLine("狗狗:爱吃");
}
}
}
class Cat
{
//监听,所以参数列表需要一致
public void Eat(object sender, FeedArgs args)
{
if (args.food != "鱼鱼")
{
Console.WriteLine("猫猫:不爱吃");
}
else
{
Console.WriteLine("猫猫:爱吃");
}
}
}
class Panda
{
//监听,所以参数列表需要一致
public void Eat(object sender, FeedArgs args)
{
if (args.food != "竹子")
{
Console.WriteLine("熊猫:不爱吃");
}
else
{
Console.WriteLine("熊猫:爱吃");
}
}
}
internal class Program
{
static void Main(string[] args)
{
Master master = new Master();
Dog dog = new Dog();
Cat cat = new Cat();
Panda panda = new Panda();
master.OnFeed += dog.Eat;
master.OnFeed += cat.Eat;
master.OnFeed += panda.Eat;
master.FeedAnimals("竹子");
Console.Read();
}
}
}
固定格式:
- EventArgs必须有,用来存储数据(存储公共数据,最后调用方法会用到的数据)
- 事件拥有者
-必须得有pubic event EventHandler Onxxx;
-public 方法:这里面这三句话必须得有。这个方法配合main接收数据。 - 事件响应者
-当作正常的类来存数据和方法
-可以被委托的方法:里面必须得有object sender, EventArgs args
-委托方法内部就当一个正常的方法来使用,中间可以使用到args的数据。 - main
-先实例化 拥有者和响应者
-Onxxx += 委托方法
-调用事件拥有者
//使用事件:enemy扣不同血
class EventArgs
{
public int attack = 0;
}
//事件拥有者
class Player
{
//事件
public event EventHandler<EventArgs> OnAttack;
public void go()
{
if(OnAttack != null)
{
EventArgs args = new EventArgs();
OnAttack(this, args);
}
}
}
//事件响应者
class SmallEnemy
{
public int blood = 100;
public string EnemyName = "小兵";
//事件处理器
public void hurt(object sender, EventArgs args)
{
args.attack = 10;
//这个时候血量就一直保持90
blood = blood - args.attack;
Console.WriteLine(EnemyName + "血量为:" + blood);
}
}
internal class Test
{
static void Main(string[] args)
{
Player player = new Player();
SmallEnemy smallEnemy1 = new SmallEnemy() ;
player.OnAttack += smallEnemy1.hurt;
player.go();
Console.Read();
}
}
事件例题
/*
* 1.英雄:大招1000,防御力为300,当英雄对怪物进行攻击时,怪物会做出反应;
2.怪物:攻击力500,防御力400,当怪物对英雄进行攻击时,英雄会做出反应;
3..如果a攻击力大于b的防御力,那a攻击b时,b会受伤害,否则就是避开
4.一个英雄对象,4只怪物,怪物编号不同;
*/
class MyArgs
{
//传入值:攻击与防御
public int attack;
public int defense;
public bool headache;
}
//拥有者
class Player
{
int attack = 1000;
int defense = 300;
public event EventHandler<MyArgs> OnPlayerAttack;
public void Do(int bossDefense)
{
if (OnPlayerAttack != null)
{
MyArgs args = new MyArgs();
args.attack = attack;
args.defense = bossDefense;
args.headache = true;
OnPlayerAttack(this, args);
}
}
}
class Boss
{
int attack = 500;
public int defense = 400;
public void Do(object sender, MyArgs args)
{
if(args.attack > defense)
Console.WriteLine("怪兽受到伤害");
if(args.headache == true)
{
Console.WriteLine("怪兽中毒了");
}
}
}
internal class Program
{
static void Main(string[] args)
{
Player player = new Player();
Boss boss = new Boss();
player.OnPlayerAttack += boss.Do;
player.Do(boss.defense);
Console.Read();
}
}
/*
* 烧水器,显示器,警报器
1.烧水器:负责烧水,温度会发生改变
2.随着温度发生改变,显示器显示的内容就发生改变;
3.当温度到达100时,发出警报声,停止烧水;
*/
class MyArgs
{
public int temperature;
}
//拥有者
class HotMachine
{
public event EventHandler<MyArgs> OnHot;
public void Do(int initTemperature)
{
if(OnHot != null)
{
MyArgs args = new MyArgs();
args.temperature = initTemperature;
OnHot(this, args);
}
}
}
//响应者
class Alarm
{
//处理
public void Tip(object sender,MyArgs args)
{
for (int i = args.temperature; i <= 100; i++)
{
Console.WriteLine("正在加热,当前温度为:" + args.temperature);
args.temperature++;
}
Console.WriteLine("热水烧好了");
}
}
internal class Program
{
static void Main(string[] args)
{
HotMachine hotmachine = new HotMachine();
Alarm alarm = new Alarm();
hotmachine.OnHot += alarm.Tip;
int initTemperature = Convert.ToInt32(Console.ReadLine());
hotmachine.Do(initTemperature);
Console.ReadLine();
}
}
只用委托的方法
/*
* 烧水器,显示器,警报器
1.烧水器:负责烧水,温度会发生改变
2.随着温度发生改变,显示器显示的内容就发生改变;
3.当温度到达100时,发出警报声,停止烧水;
*/
//拥有者
class HotMachine
{
public delegate void OnHot(int temperature);
public OnHot onhot = null;
public void Do(int initTemperature)
{
if(onhot!= null)
{
onhot(initTemperature);
}
}
}
//响应者
class Alarm
{
public void tip(int initTemperature)
{
for (int i = initTemperature; i <= 100; i++)
{
Console.WriteLine("当前温度:"+i);
}
Console.WriteLine("热水烧好了!");
}
}
internal class Program
{
static void Main(string[] args)
{
HotMachine hotmachine = new HotMachine();
Alarm alarm = new Alarm();
hotmachine.onhot += alarm.tip;
int initTemperature = Convert.ToInt32(Console.ReadLine());
hotmachine.Do(initTemperature);
Console.Read();
}
}