编辑
2023-05-19
编程语言
00

目录

委托
委托的基本概念
委托的声明与使用
内置委托类型
事件的基本概念
事件的声明与使用
自定义事件参数
访问限制
线程安全
常见事件安全的问题
如何避免
委托和事件的区别

委托

委托的基本概念

  • 定义:委托是一种类型安全的函数指针,允许将方法作为参数传递或赋值给变量。
  • 作用:实现方法的回调机制,支持事件驱动编程。

委托的声明与使用

  • 声明语法

    csharp
    public delegate void MyDelegate(string message);
  • 实例化与调用

    csharp
    MyDelegate del = new MyDelegate(MethodName); del("Hello, World!");

多播委托(Multicast Delegate)

  • 定义:一个委托可以同时引用多个方法。
  • 添加方法:使用 += 运算符添加方法。
  • 移除方法:使用 -= 运算符移除方法。

内置委托类型

  • Action:表示无返回值的方法。

    csharp
    Action<string> action = Console.WriteLine;
  • Func:表示有返回值的方法。

    csharp
    Func<int, int, int> add = (x, y) => x + y;

委托的高级特性

  • 匿名方法:使用 delegate 关键字定义匿名方法。

    csharp
    MyDelegate del = delegate(string msg) { Console.WriteLine(msg); };
  • Lambda 表达式:简化的匿名方法写法。

    csharp
    MyDelegate del = (msg) => Console.WriteLine(msg);

事件

事件的基本概念

  • 定义:事件是对委托的封装,用于实现发布-订阅模式,使对象之间能够进行松耦合的通信。
  • 作用:当某个动作发生时,事件可以通知所有订阅者,从而触发相应的处理逻辑。

事件的声明与使用

  • 声明事件

    event(关键字) + 委托类型 + 事件名

    csharp
    //以下为C#中自带的委托类型 //Action类型皆不带返回值,可带参数 public event Action OnTest1;//不带参数 public event Action<int> OnTest2;//带一个int类型参数 //Func类型皆带返回值,可带参数(最后一个为返回值) public event Func<int> OnTest3; public event Func<string, int> OnTest4;//带一个string类型参数,返回int类型 //自定义委托类型 public event MyDelegate CustomEvent; public delegate void MyDelegate(string msg);

    特殊的自带委托类型,EventHander

    csharp
    public event EventHandler<T> evHandler;

    其中T必须继承自EventArgs,也就是说T至少是个class

    • eventHandler注册事件
    csharp
    evHandler += (object sender, EventArgs e) => { //sender获取到事件的发送者 if (sender is Test) { //获取到事件传递过来的参数 e.xxxx } };
    • eventHandler触发事件
    csharp
    //触发Handler事件,this指的是事件的发送者 evHandler?.Invoke(this, EventArgs.Empty);
  • 订阅事件

    csharp
    MyEvent += new EventHandler(MyEventHandler);
  • 触发事件

    csharp
    MyEvent?.Invoke(this, EventArgs.Empty);

事件的组成部分

  • 发布者(Publisher):定义并触发事件的对象。
  • 订阅者(Subscriber):订阅并响应事件的对象。
  • 事件处理器(EventHandler):处理事件的委托方法。

自定义事件参数

  • 定义自定义参数类

    csharp
    public class MyEventArgs : EventArgs { public string Message { get; set; } }
  • 声明带有自定义参数的事件

    csharp
    public event EventHandler<MyEventArgs> MyEvent;
  • 触发事件并传递参数

    csharp
    MyEvent?.Invoke(this, new MyEventArgs { Message = "Hello" });

事件的注意事项

访问限制

事件只能由声明它的类触发,外部类只能订阅或取消订阅事件,不能触发事件。

线程安全

事件在多线程执行和异步调用时可能出现的问题

  • 多个线程同时订阅(+=)或取消订阅(-=)
  • 一个线程在触发事件,另一个线程取消了订阅
  • 异步任务中调用事件

常见事件安全的问题

  1. NullReferenceException(空引用异常)

    csharp
    MyEvent?.Invoke(this, EventArgs.Empty);

    如果在判断 MyEvent != null 后,另一个线程刚好将所有订阅者移除,会导致 MyEvent 在调用时变为 null,从而抛出异常。

  2. 委托调用执行异常或遗漏

    当多个线程同时执行:

    csharp
    MyEvent += HandlerA; MyEvent += HandlerB;

    这可能破坏底层的委托链,导致:

    • 方法调用顺序异常;
    • 某些订阅丢失或遗漏;
    • 程序行为不一致。

如何避免

一般框架中都会封装好事件系统,避免我们手动调用导致的线程安全的问题,比如GameFramework

  • 使用临时变量装载后再调用,这样当有其他线程-=移除了,我们逻辑还可以继续执行而不会抛出空引用
csharp
EventHandler handler = MyEvent; if (handler != null) { handler(this, EventArgs.Empty); }
  • 空判断后再调用
csharp
MyEvent?.Invoke(this, EventArgs.Empty);

委托和事件的区别

  • 事件:是对委托的封装,用于发布-订阅模式,提供更安全的访问机制。

  • 声明事件

    csharp
    public event MyDelegate MyEvent;
  • 触发事件

    csharp
    MyEvent?.Invoke("Event triggered");
对比项委托(Delegate)事件(Event)
本质类型安全的函数指针(封装方法引用)基于委托的封装机制
语法定义public delegate void MyDelegate();public event MyDelegate MyEvent;
访问权限可被外部代码直接调用、赋值、清空,不安全外部代码只能 += 或 -= 订阅与取消,不能调用
调用方式外部和内部代码都可以调用只能在声明它的类内部调用
封装性无封装(委托字段是公开变量)提供了封装(事件只能通过订阅机制访问)
多播支持支持(委托可组合多个方法)支持(事件本质是多播委托)
使用场景回调、策略模式、函数链式调用发布-订阅模式(UI 事件、系统事件、状态通知)
默认线程安全❌ 否,操作需自行加锁或复制引用❌ 否,同样需加锁或拷贝引用触发
是否必须依赖独立存在必须基于委托类型

💡 总结:事件是对委托的进一步封装,用于发布-订阅模型,限制外部随意触发调用,使程序结构更安全、清晰。

本文作者:xuxuxuJS

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!