C# 图解教程 第 15 章 事件(笔记+模仿代码)

本文深入探讨了发布者/订阅者模式的实现原理及应用,通过实例代码演示了如何在C#中声明事件、订阅事件、触发事件,以及如何使用标准事件。介绍了事件的组成部分,包括委托类型声明、事件处理程序声明等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15.1 发布者和订阅者

发布者 / 订阅者模式:发布者类定义了一系列程序的其他部分可能感兴趣的事件,其他一些订阅者类可以向发布者提供一个方法来“注册”以获取通知。当事件发生时,发布者“触发事件”,然后依次执行订阅者提交的所有事件。

实际上事件包含了一个私有的委托,对于事件我们只能添加、删除或调用事件处理程序。

15.2 源代码组件预览

  • 委托类型声明;
  • 事件处理程序声明;
  • 事件声明;
  • 事件注册;
  • 触发事件的代码。

15.3 声明事件

public event [委托类型] [事件名];
public event EventHandler CountADozen;	// 也可以加 static 使其变成静态的

15.4 订阅事件

类似委托,用+=可以添加事件处理程序。

15.5 触发事件

触发事件之前要和null进行比较,从而查看事件是否包含事件处理程序。

下面的代码包含了前 5 个小节的知识点,这段代码描述了一个玩家从坐标 (0,0) 开始直线走到 (100, 0),再走到 (100, 100),(0, 100),最后回到原点。每当他走到拐角(不包括出发时)的时候会触发一次事件,事件包含两个处理程序:报告当前位置以及模拟一次普通攻击。

using System;

namespace Test
{
    delegate void Handler(Player player);	// 声明委托
    class Player
    {
        public int coordinateX, coordinateY;	// 二维坐标
        public event Handler AtTheCorner;		// 创建事件并发布
        public void MoveOneTrip()	// 从 (0,0) 开始沿着正方形走一圈回到起点
        {
            coordinateX = coordinateY = 0;
            while (++coordinateX < 100)
                continue;
            AtTheCorner?.Invoke(this);		// 每当遇到转角时触发一次事件
            while (++coordinateY < 100)
                continue;
            AtTheCorner?.Invoke(this);		// ?. 运算符简化了判空
            while (--coordinateX > 0)
                continue;
            AtTheCorner?.Invoke(this);
            while (--coordinateY > 0)
                continue;
            AtTheCorner?.Invoke(this);
        }
    }
    class PlayerShowInformation
    {
        public PlayerShowInformation(Player player)
        {
            player.AtTheCorner += SayHello;		// 订阅事件
        }
        void SayHello(Player player)	// 报告此时的坐标位置
        {
            Console.WriteLine("Hey, I am at the corner now!");
            Console.WriteLine("My position is : {0}, {1}."
                , player.coordinateX, player.coordinateY);
        }
    }
    class PlayerAttack
    {
        public PlayerAttack(Player player)
        {
            player.AtTheCorner += NormalAttack;	// 订阅事件
        }
        void NormalAttack(Player player)	// 模拟普通攻击,攻击力为两个坐标之和
        {
            Console.WriteLine("Normal attack --- {0} damage.\n"
                , player.coordinateX + player.coordinateY);
        }
    }
    class Program
    {
        static void Main()
        {
            Player noobPlayer = new Player();
            PlayerShowInformation noobPlayerInformation = new PlayerShowInformation(noobPlayer);
            PlayerAttack noobPlayerAttack = new PlayerAttack(noobPlayer);
            noobPlayer.MoveOneTrip();	// 触发事件
        }
    }
}

15.6 标准事件的用法

和上面的代码做了同样的事情,但这次使用了System命名空间中声明的EventHandler委托类型,以及扩展的EventArgs来传递数据。

在事件的两个处理程序中使用了两种不同的传递数据的方法:

  • 在第 45 行SayHello方法中用了EventHandler的第一个参数,即触发事件的对象的引用,通过强制类型转换来得到对象成员的值;
  • 在第 59 行NormalAttack方法中用了泛型委托,使用自定义类MyEventArgs来代替原本的第二个参数,并通过这个类来传递数据。
using System;
using System.Data;

namespace Test
{
    public class MyEventArgs : EventArgs
    { 
        public int X { get; set; }
        public int Y { get; set; }
        public MyEventArgs Update(int x, int y)
        {
            X = x;
            Y = y;
            return this;
        }
    }
    class Player
    {
        MyEventArgs args = new MyEventArgs();
        public int coordinateX, coordinateY;
        public event EventHandler<MyEventArgs> AtTheCorner;  // 泛型委托
        public void MoveOneTrip()
        {
            coordinateX = coordinateY = 0;
            while (++coordinateX < 100)
                continue;
            AtTheCorner?.Invoke(this, args.Update(coordinateX, coordinateY));    // 使用自定义的参数类
            while (++coordinateY < 100)
                continue;
            AtTheCorner?.Invoke(this, args.Update(coordinateX, coordinateY));
            while (--coordinateX > 0)
                continue;
            AtTheCorner?.Invoke(this, args.Update(coordinateX, coordinateY));
            while (--coordinateY > 0)
                continue;
            AtTheCorner?.Invoke(this, args.Update(coordinateX, coordinateY));
        }
    }
    class PlayerShowInformation
    {
        public PlayerShowInformation(Player player)
        {
            player.AtTheCorner += SayHello;
        }
        void SayHello(object source, MyEventArgs e)
        {
            Player player = (Player)source;
            Console.WriteLine("Hey, I am at the corner now!");
            Console.WriteLine("My position is : {0}, {1}."
                , player.coordinateX, player.coordinateY);
        }
    }
    class PlayerAttack
    {
        public PlayerAttack(Player player)
        {
            player.AtTheCorner += NormalAttack;
        }
        void NormalAttack(object source, MyEventArgs e)
        {
            Console.WriteLine("Normal attack --- {0} damage.\n", e.X + e.Y);
        }
    }
    class Program
    {
        static void Main()
        {
            Player noobPlayer = new Player();
            PlayerShowInformation noobPlayerInformation = new PlayerShowInformation(noobPlayer);
            PlayerAttack noobPlayerAttack = new PlayerAttack(noobPlayer);
            noobPlayer.MoveOneTrip();
        }
    }
}

15.7 事件访问器

事件只允许+=-=运算符,但是我们可以为事件定义addmove两个事件访问器来改变两个运算符的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值