【C#进阶】C# 事件

news/2024/5/20 0:28:43 标签: c#, 开发语言, .net, .netcore, 微软
序号系列文章
15【C#进阶】C# 属性
16【C#进阶】C# 索引器
17【C#进阶】C# 委托

文章目录

  • 前言
  • 1、什么是事件
    • 1.1、发布订阅模型的说明
  • 2、事件的声明
  • 3、事件的使用
    • 3.1、使用基类中的事件
    • 3.2、接口中定义事件
    • 3.3、自定义方法访问事件
  • 4、事件与委托的异同:
  • 结语

前言

🌍 hello大家好啊,我是哈桑。本文为大家介绍 C# 中的事件。


1、什么是事件

事件本质上来讲是一种特殊的多播委托1,只能从声明它的类中进行调用。事件通常用于表示用户操作,例如单击按钮或图形用户界面中的菜单选项。C# 中常常会使用事件来实现线程之间的通信。

1.1、发布订阅模型的说明

在 C# 中,类或对象可以通过事件向其他类或对象通知发生的相关事情。这种模式通常称为发布订阅模型,发送(或引发)事件的类称为“发布者”,接收(或处理)事件的类称为“订阅者”。

对发布者和订阅者的解释说明:

发布者: 一个创建了事件和委托定义的对象,同时也包含了事件和委托之间的联系与具体行为。发布者的任务就是执行这些事件,并通知程序中的其它对象。
订阅者: 一个接收事件并提供事件处理程序的对象。订阅者中的方法(事件处理程序)用于分配给发布者中的委托。
简单的来说,发布者确定何时引发事件,而订阅者确定对事件作出何种响应。

代码示例:(简单实现)

using System; 

// 发布者类 
public class PublisherClass
{
    // 和事件搭配的委托
    public delegate void PubDelegate();

    // 定义事件 
    public event PubDelegate PubEvent;

    // 编写处理事件的具体逻辑
    public void EventHandling()
    {
        if (PubEvent == null)
        {
            Console.WriteLine("需要注册事件的啊");
        }
        else
        {
            // 执行注册的事件 
            PubEvent(); 
        }
    }
}

// 订阅者类 
public class SubscriberClass
{
    public void printout()
    {
        Console.WriteLine("执行了订阅者类中的事件。");
        Console.ReadLine(); 
    }
}

public class Program
{
    static void Main()
    {
        // 实例化对象 
        PublisherClass p = new PublisherClass();
        SubscriberClass s = new SubscriberClass();
        // 执行事件 
        p.EventHandling();
        // 注册事件 
        p.PubEvent += new PublisherClass.PubDelegate(s.printout); 
        // 执行事件 
        p.EventHandling();  
    }
}

运行结果:
在这里插入图片描述
在上例中,创建了发布者类 PublisherClass 和订阅者类 SubscriberClass,直接把订阅者类中的 printout 方法传递给了 PublisherClass.PubEvent 方法用于执行。(发布订阅模型的简单思路就是这样, 在正式的项目中程序之间的交互与通信表现得更加复杂。)

2、事件的声明

首先需要注意的是,因为事件的本质还是委托,所以要声明一个事件之前必须先声明一个相对应的委托。

以上面示例的代码来说明:

// 和事件搭配的委托
public delegate void PubDelegate();

在 C# 中,事件需要使用关键字 event,事件的声明语法可以总结为如下所示:

<Access Specifier > event <Delegate> <Event Name>

  • Access Specifier: 访问说明符
  • event: 声明事件必须要有的关键字
  • Delegate: 分配给事件的委托(事先声明好的)
  • Event Name: 事件的名称

示例代码:

// 定义事件 
public event PubDelegate PubEvent;

3、事件的使用

事件的基本使用在上面的发布订阅模型的说明中已经演示了,这里不再赘述。接下来以一个新的示例来说明事件在基类、接口中的定义和如何自定义方法访问事件的操作。

3.1、使用基类中的事件

子类可以继承使用基类中已经声明的事件,像这种使用基类中的事件的模式广泛用于 .NET 类库中的 Windows 窗体2类。 以一个简单的程序来演示子类如何使用基类中的事件。

代码示例:

namespace BaseClassEvents 
{
    using System;

    // 基类事件发布者
    public abstract class Shape
    {
        protected double _area;

        public double Area
        {
            get { return _area; }
            set { _area = value; }
        }

        // 声明一个与事件搭配的委托
        public delegate void ShapeDelegate();

        // 声明基类中的事件 
        public event ShapeDelegate ShapeEvent;

        // 抽象方法
        public abstract void Drow();
        public abstract void GetArea();

        // 执行已经注册的事件 
        public void EventHandling()
        {
            if (ShapeEvent != null)
            {
                ShapeEvent();
            }
            else
            {
                Console.WriteLine("需要注册事件的");
            }
        }
    }

    // 圆形 
    public class Circle : Shape
    {
        private double _radius;

        public Circle(double radius)
        {
            _radius = radius;
            _area = 3.14 * _radius * _radius;
        }

        public override void Drow()
        {
            Console.WriteLine("绘制了一个圆形");
        }

        public override void GetArea()
        {
            Console.WriteLine($"圆形的面积为{Area}");
        }
    }

    // 矩形
    public class Rectangle : Shape
    {
        private double _length;
        private double _width;

        public Rectangle(double length, double width)
        {
            _length = length;
            _width = width;
            _area = _length * _width;
        }

        public override void Drow()
        {
            Console.WriteLine("绘制了一个矩形");
        }

        public override void GetArea()
        {
            Console.WriteLine($"矩形的面积为{Area}");
        }
    }

    // 将形状的行为添加道基类的事件里去 
    public class ShapeContainer
    {
        public void AddMethod(Shape shape)
        {
            shape.ShapeEvent += shape.Drow;
            shape.ShapeEvent += shape.GetArea;

            // 执行基类中已经注册的事件 
            shape.EventHandling();
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            var circle = new Circle(11);
            var rectangle = new Rectangle(11, 11);
            var container = new ShapeContainer();

            container.AddMethod(circle);
            container.AddMethod(rectangle);
        }
    }
}

运行结果:
在这里插入图片描述在上面的示例中,创建了 Circle 和 Rectangle 两个形状类并继承了 Shape 基类, 在 ShapeContainer.AddMethod 方法中使用 shape 参数将指定类方法添加基类的 ShapeEvent 事件中,这样就可以在子类中使用基类中的 EventHandling 方法,以此达到使用基类中的事件的目的。

3.2、接口中定义事件

不仅是在类中,在接口中也可以声明事件,称为接口事件。接口事件的实现和接口上的方法或属性的实现是一样的,以一个示例来说明如何在类中实现接口事件。

代码示例:

namespace ImplementInterfaceEvents
{
    public interface IDrawingObject
    {
        // 所有继承该接口的对象都需要创建 ShapeChanged 事件
        event EventHandler ShapeChanged;        
    }

    public class MyEventArgs : EventArgs
    {
        // 构造方法 
        public MyEventArgs()
        {
            Console.WriteLine("执行了 MyEventArgs 类");
        }
    }

    public class Shape : IDrawingObject
    {
        public event EventHandler ShapeChanged;

        public void ChangeShape()
        {
            // 在活动开始前做点什么… 
            MyEventArgs m = new MyEventArgs();

            OnShapeChanged(m);
            // 或者事后在这里做点什么。 
        }

        protected virtual void OnShapeChanged(MyEventArgs e)
        {
            ShapeChanged?.Invoke(this, e);
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Shape s = new Shape();
            s.ChangeShape();
        }
    }
}

运行结果:
在这里插入图片描述
在上面的示例,Shape 类继承 IDrawingObject 接口并实现了 ShapeChanged 事件。在 ChangeShape 方法中将 MyEventArgs 方法注册到了 ShapeChanged 事件中并调用。

3.3、自定义方法访问事件

在大多数情况下,是无需提供自定义事件访问器的。但是某些特殊情况下,就需要自定义事件访问器,比方说当类继承自两个或多个接口,且每个接口都具有相同名称的事件。这时就必须为至少其中一个事件提供显式接口实现。 为事件编写显式接口实现时,还必须编写 add 和 remove 事件访问器。当遇到这种情况时就需要自己定义一个事件访问器。

通过提供自己的访问器,可以指定两个事件是由类中的同一个事件表示,还是由不同事件表示。 例如,如果根据接口规范应在不同时间引发事件,可以在类中将每个事件与单独实现关联。

代码示例:

namespace ImplementInterfaceEvents
{
    public interface IDrawingObject
    {
        // 所有继承该接口的对象都需要创建 ShapeChanged 事件
        event EventHandler ShapeChanged;
    }

    public class MyEventArgs : EventArgs
    {
        // 构造方法 
        public MyEventArgs()
        {
            Console.WriteLine("执行了 MyEventArgs 类");
        }
    }

    public class Shape : IDrawingObject
    {
        //为每个接口事件创建一个事件 
        event EventHandler DrawEvent;

        // 自定义实现 
        event EventHandler IDrawingObject.ShapeChanged
        {
            add { DrawEvent += value; }
            remove { DrawEvent -= value; }
        }

        public void ChangeShape()
        {
            // 在活动开始前做点什么… 
            MyEventArgs m = new MyEventArgs();
            OnShapeChanged(m);
            // 或者事后在这里做点什么。 
        }

        protected virtual void OnShapeChanged(MyEventArgs e)
        {
            DrawEvent?.Invoke(this, e);
        }
    }

    public class Program
    {
        static void Main(string[] args)
        {
            Shape s = new Shape();
            s.ChangeShape();
        }
    }
}

在接口事件的示例基础上进行的改动:
在这里插入图片描述
点击了解更多自定义事件访问器的使用。

4、事件与委托的异同:

  • 相同点:
    • 事件其实是一个多播委托,本质上是一样的。
  • 不同点:
    • 可调用位置不同:事件只能在声明事件的类中才能调用,而委托无论是在类的内部还是外部都可以调用。
    • 可使用符号不同:事件只能使用 += 和 -= 符号来订阅和取消订阅,但是委托不仅可以使用 += 和 -= 符号还可以使用 = 符号进行方法分配。

点击了解更多事件的使用。


结语

🌎 以上就是 C# 事件的介绍啦,希望对大家有所帮助。感谢大家的支持。


  1. 多播委托: 就是一个委托同时绑定多个方法,多播委托也叫委托链,委托组合。 ↩︎

  2. Windows 窗体: Windows 窗体是用于生成 Windows 桌面应用的 UI 框架。 ↩︎


http://www.niftyadmin.cn/n/178995.html

相关文章

如何在 Python 中混合使用同步和异步函数?

前言 异步编程可以提高应用程序的性能和吞吐量&#xff0c;因为它可以充分利用 CPU 和 I/O 资源。当某个任务被阻塞时&#xff0c;事件循环可以切换到另一个任务&#xff0c;从而避免浪费 CPU 时间。此外&#xff0c;异步编程还可以简化代码&#xff0c;使其更易于维护和调试。…

更快更稳更优质:华为云CDN下载加速解决方案测评

目录 一、前言 二、下载业务面临的三大难题 1、业务下载缓慢 2、业务稳定性受挑战 3、高昂的运维成本 三、完美解决方案&#xff1a;CDN下载加速 四、为什么选择华为云&#xff1f; 1、快速高效&#xff0c;极致体验 2、稳定可靠&#xff0c;优质服务 3、智能缓存&am…

Docker搭建私有仓库

Docker搭建私有仓库 一、私有仓库搭建 拉取私有仓库镜像 docker pull registry启动私有仓库容器 docker run -id --nameregistry -p 5000:5000 registry打开浏览器 输入地址http://私有仓库服务器ip:5000/v2/_catalog&#xff0c;看到{“repositories”:[]} 表示私有仓库 搭…

c# 第一次作业

一. 单选题&#xff08;共49题&#xff0c;93.1分&#xff09; 1. (单选题)下面属于合法变量名的是_____________。 A. P_qrB. 123mnpC. BooleanD. X-Y 我的答案: A正确答案: A 1.9分 2. (单选题)用所有.NET支持的编程语言编写的源代码经过一次编译后被编译成_____________…

SpringMVC的请求处理

文章目录一、请求映射路径的配置二、请求数据的接收1. 接收普通请求数据2. 接收数组或集合数据3. 接收实体JavaBean属性数据4. 接收Json数据格式数据5. 接收Restful风格数据三、Javaweb常用对象获取四、请求静态资源五、注解驱动 < mvc:annotation-driven> 标签一、请求映…

js中的按位运算符详解‘‘ , ‘|‘, ‘^‘, ‘~‘, ‘<<‘, ‘>>‘, ‘>>>‘

一、什么是按位运算符 按位运算符是JavaScript中的一组二进制运算符&#xff0c;用于对数字的二进制表示进行操作。按位运算符会将操作数转换成二进制形式&#xff0c;然后对它们的每一位进行比较和计算&#xff0c;最终得出运算结果。 二、常用的按位运算符 按位与运算符 &…

2023年全国最新二级建造师精选真题及答案34

百分百题库提供二级建造师考试试题、二建考试预测题、二级建造师考试真题、二建证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 6.关于工伤保险&#xff0c;下列说法正确的是&#xff08;&#xff09;。 A.事业单位、社会团…

强制缓存与协商缓存

强缓存的优先级高于协商缓存&#xff0c;而在协商缓存中&#xff0c;Etag优先级比last-modify高 其他参考资料 都知道http的请求头和响应头都可以设置cache-control属性&#xff0c;它的作用是用来设置静态资源缓存的。难道他们就没有什么不一样的地方么&#xff1f;反正一开始…