C# 事件

事件 (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();
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值