概念
一个对象在完成某个工作后或者发生了某种操作后,需要通知其他对象,从而做出反应;发送出去的通知就是事件
场景: 玩家发动范围攻击三名敌人和一名NPC
class Program
{
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.BeAttackedTest;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
//object o —— 谁打的我; attack —— 减了多少血量
public delegate void OnAttackDelegate(object o, int attack);
public OnAttackDelegate OnAttack = null;
//执行方法发动范围攻击
public void DoAOE()
{
if(OnAttack != null)
{
OnAttack(this,10);
}
}
public void Shout()
{
Console.WriteLine("玩家很疼");
}
}
class Enemy
{
private int blood = 100;
public void MinusBlood(object o,int attack)
{
Console.WriteLine("好疼!我是Enemy");
blood -= attack;
//发动技能 反甲
Player p = (Player) o;
p.Shout();
}
}
class NPC
{
private int blood = 100;
public void BeAttackedTest(object o,int attack)
{
Console.WriteLine("好疼!我是NPC");
blood -= attack;
}
}
场景:玩家的范围攻击升级附加毒属性
class Program
{
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.BeAttackedTest;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
//object o —— 谁打的我;attack —— 减了多少血量;poisoned —— 是否中毒
public delegate void OnAttackDelegate(object o, int attack ,bool poisoned);
public OnAttackDelegate OnAttack = null;
public void DoAOE()
{
if(OnAttack != null)
{
OnAttack(this,10,true);
}
}
public void Shout()
{
Console.WriteLine("玩家很疼");
}
}
class Enemy
{
private int blood = 100;
public void MinusBlood(object o,int attack,bool poisoned) //poisoned是否有毒属性攻击
{
Console.WriteLine("好疼!我是Enemy");
blood -= attack;
//发动技能 反甲
Player p = (Player) o;
p.Shout();
//判断是否中毒
if(poisoned)
{
Console.WriteLine("中毒了!我是Enemy");
}
}
}
class NPC
{
private int blood = 100;
public void BeAttackedTest(object o,int attack,bool poisoned)
{
Console.WriteLine("好疼!我是NPC");
blood -= attack;
}
}
问题:委托方法,需要携带 的参数太多了,参数还会经常变动(毒属性,以后再加眩晕)
解决办法:将所有参数都包含到一个class里面
class Program
{
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.BeAttackedTest;
}
}
//使用委托解耦合
//在player当中声明委托类型,将需要调用的减血方法在Player类外设置给内部的委托
class Player
{
//声明委托,用来规定减血的方法应该符合怎样的方法签名
//object o谁发动的攻击;EventArgs 攻击种类集合
public delegate void OnAttackDelegate(object o, EventArgs args);
public OnAttackDelegate OnAttack = null;
public void DoAOE()
{
if(OnAttack != null)
{
EventArgs args = new EventArgs(); //给攻击种类赋值
args.attack = 10;
args.poisoned = true;
OnAttack(this,args);
}
}
public void Shout()
{
Console.WriteLine("玩家很疼");
}
}
class Enemy
{
private int blood = 100;
public void MinusBlood(object o,EventArgs args)
{
Console.WriteLine("好疼!我是Enemy");
blood -= args.attack;
//发动技能 反甲
Player p = (Player) o;
p.Shout();
//判断是否中毒
if(args.poisoned)
{
Console.WriteLine("中毒了!我是Enemy");
}
}
}
class NPC
{
private int blood = 100;
public void BeAttackedTest(object o,EventArgs args)
{
Console.WriteLine("好疼!我是NPC");
blood -= args.attack;
}
}
class EventArgs //事件参数们
{
public int attack = 0; //攻击
public bool poisoned = false; //毒属性
}
- C#写好了事件专用委托定义
//sender 事件的发出者 e 所要执行的事件类
public delegate void EventHandler(object? sender,EventArgs e);
- 在场景中使用
class Player
{
public EventHandler OnAttack = null;
}
进一步思考事件
- 事件对应的委托,不应该被类外界调用,只能由某个操作触发
- 事件对应的委托,不应该被类外界 直接赋值,只能够通过+、-增减委托方法
关键字 event
public event EventHandler OnAttack = null;
事件规则
- 加event关键字修饰的委托,只能够定义在某个类内;
- 加event关键字修饰的委托,只能够被当前类内方法触发执行;类外不可触发执行
- 加event关键字修饰的委托,只能通过+、-增减委托方法,不可赋值
class Program
{
static void Main(string[] args)
{
Player player = new Player();
Enemy e = new Enemy();
player.OnAttack += e.AttackMe;
p.DoAOE();
//错误示范: 在类外直接调用event修饰的委托去执行,是禁止的
//player.OnAttack(new object,EventArgs.Empty);
}
}
class Player
{
//定义Player内部会被触发的事件委托
public event EventHandler OnAttack = null;
public void DoAOE()
{
if(OnAttack != null)
{
OnAttack(this,EventArgs.Empty);
}
}
}
class Enemy
{
public void AttackMe()
{
Console.WriteLine("我被攻击了");
}
}
事件中的角色
事件参数泛型
- 事件被触发的时候,可以传送自定义的事件参数
步骤:
- 定义自己的事件参数包class
- 将EventHandler特化成传输咱们自己的参数包类的委托类型
- 响应方法中,将事件参数包类型替换成我们自己的参数包类型
class Program
{
static void Main(string[] args)
{
Play p = new Player();
Enemy e = new Enemy();
p.OnAttack += e.AttackMe;
p.DoAOE();
}
}
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.poisoned);
Console.WriteLine("是否眩晕" + args.headache);
}
}
class MyArgs
{
public int attack = 0; //攻击
public bool poisoned = false; //中毒
public bool headache = false; //眩晕
}
练习一
public static class Program10
{
/*Input Manager 练习
*
* 编写一个InputManager的Class,用来接收用户输入的一个字符,之后调用OnInput事件,
* 本事件会传导给监听者两个东西(object sender,InputArgs args),args里面携带着用户输入的字符
*/
public static void Main()
{
InputManager im = new InputManager();
im.OnInput += OnKeyInput;
im.WaitForInput();
}
static void OnKeyInput(object sender, InputArgs args)
{
Console.WriteLine("收到了OnInput事件,拿到了字符:" + args.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());
//Console.WriteLine(input);
//2.调用Onput事件,将字符传导到监听方法/监听者
if (OnInput != null)
{
InputArgs args = new InputArgs();
args.input = input;
OnInput(this, args);
}
}
}
}
class InputArgs
{
public char input;
}
练习二
编写人类喂食宠物的过程:
- 事件源:人类,人可以发出喂食的事件
- 事件参数:喂的什么食物
- 事件订阅者:狗/猫/ 熊猫
- 事件处理方法:动物们的OnFeed方法,判断当前食物是否爱吃
/*喂食宠物
* 模拟主人喂食宠物的过程,三个宠物狗/猫/熊猫
* 事件源:主人(OnFeed)
* 监听者:狗 猫 熊猫 Eat
* 事件参数包: string food;
*/
public static class Program11
{
public static void Main()
{
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("竹子");
}
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 FeedArgs
{
public string food = "";
}
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("熊猫:爱吃");
}
}
}
}