2023-05-19
编程语言
00

目录

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

委托

委托的基本概念

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

委托的声明与使用

  • 声明语法

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

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

多播委托(Multicast Delegate)

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

委托与事件的关系

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

  • 声明事件

    csharp
    public event MyDelegate MyEvent;
  • 触发事件

    csharp
    MyEvent?.Invoke("Event triggered");

内置委托类型

  • 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);

事件

事件的基本概念

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

事件的声明与使用

  • 声明事件

    csharp
    public event EventHandler MyEvent;
  • 订阅事件

    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);

委托和事件的区别

C# 委托(Delegate)与事件(Event)的区别总结

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

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

本文作者:xuxuxuJS

本文链接:

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