C# 委托与事件

简介

委托(Delegate)是一种类型,它可以存储对方法的引用,允许您将方法作为参数传递给其他方法,或者从方法返回另一个方法。委托提供了一种灵活的方式来实现事件处理、回调函数和异步编程。

本质

在 C# 中,委托实际上是一个类型安全的函数指针,它封装了对一个或多个方法的引用。当创建一个委托时,实际上创建了一个委托的实例,这个实例是委托类的对象。这个实例可以持有一个或多个方法的引用,并且可以像调用普通方法一样调用委托。

Delegate类是一个抽象基类,C#中的委托通常都是MulticastDelegate类的实例,MulticastDelegate 类提供了多播委托的支持,允许一个委托对象同时持有多个方法引用。总之,委托是一个类,它封装了方法的引用,并且可以持有一个或多个方法的引用。它通过 MulticastDelegate 类提供了多播委托的支持,使得可以同时调用多个方法。

字段初始化

在初始化一个委托对象时,会先初始化三个重要的字段:

  • _target: 这是一个指向委托调用的目标对象的引用。对于实例方法的委托,目标将是实例对象的引用,而对于静态方法的委托,目标将是 null。
  • _methodPtr: 这是一个指向委托调用的方法的指针。对于静态方法,它是一个指向静态方法的指针。对于实例方法,它是一个指向该方法的委托的实例的指针。
  • _invocationList: 当一个委托具有多个方法时,每个方法都会被包装成一个委托,并添加到委托链中。这个字段包含一个数组,其中包含委托链中的所有委托。

方法包装

当委托对象包装一个方法时,它会将目标方法的引用存储在 _methodPtr 字段中,而 _target 字段将指向目标对象(如果是实例方法),_invocationList 字段则保持为 null。

包装静态方法和实例方法的区别

当主要的区别在于实例方法需要一个对象实例来调用,而静态方法则不需要。

静态方法:静态方法是在类级别上定义的方法,它们不依赖于特定的对象实例。因此,当一个委托包装一个静态方法时,你只需要指定方法的名称即可。调用委托时,它会直接调用静态方法,不需要提供实例对象。

public static void StaticMethod()
{
    // 静态方法的实现
}

// 创建一个委托来包装静态方法
Action staticDelegate = StaticMethod;

实例方法:实例方法是在对象实例级别上定义的方法,它们依赖于特定的对象实例。当一个委托包装一个实例方法时,需要指定实例对象,并且该对象必须是方法所属的类的一个实例。调用委托时,它会使用指定的实例对象调用实例方法。

public class MyClass
{
    public void InstanceMethod()
    {
        // 实例方法的实现
    }
}

// 创建一个 MyClass 的实例
MyClass myInstance = new MyClass();

// 创建一个委托来包装实例方法
Action instanceDelegate = myInstance.InstanceMethod;

总的来说,区别在于静态方法不需要对象实例,而实例方法需要一个特定的对象实例来调用。

委托链

委托链是由多个委托对象组成的链式结构,每个委托对象都包含了一个方法的引用。而 _invocationList 字段存储了这些委托对象的引用,实际上是一个委托对象数组。这个数组中的每个元素都代表一个委托对象,每个委托对象都可以包含一个方法的引用。

通过 _invocationList 字段,可以在多播委托中获取到每个委托对象,进而对委托链进行遍历或操作。

 

我们常说委托链是通过委托对象的链式连接来实现的。注意:这里“链式结构”的说法可能是基于委托组合和调用的行为,而不是实际的数据结构。在多播委托的上下文中,“链式”更多指的是委托组合后的一种逻辑调用顺序,即当你组合多个委托时,这些委托将按照添加的顺序依次调用,就像链条上的每个环节依次被触发一样。 实际实现是数组存储,而“链式结构”更多的是描述委托调用顺序的抽象概念。  

事件

首先事件的实质就是委托,他是一个受限制的委托,只能施加+=,-=操作符,事件与委托二者本质上是一个东西。

可以将事件理解为对委托的一种封装。事件提供了一种更高级别的抽象,它将委托的功能进行了封装,并添加了额外的语义和功能,以确保更安全、更可靠的事件通知机制。

隐藏了委托的具体细节,提供了更简洁、更易于使用的接口。通过事件,发布者和订阅者之间的交互变得更加清晰,同时保护了委托链的安全性,防止了外部代码对委托链的意外修改。

委托 VS 事件

  • 访问权限
  • 委托:任何对象都可以添加、移除和调用委托。
  • 事件:只有声明事件的类或结构可以触发(调用)事件,其他对象只能订阅或取消订阅。
  • 用法和目的
  • 委托:用作回调函数或函数指针,允许方法作为参数传递。
  • 事件:用于发布-订阅模式,通知多个订阅者某个事件发生。
  • 语法和实现
  • 委托:可以直接实例化和调用。
  • 事件:基于委托实现,提供了额外的封装和访问控制。

优点

  1. 灵活性: 委托提供了一种灵活的方式来实现回调机制,允许你将方法作为参数传递给其他方法,从而增强了程序的灵活性和可扩展性。

  2. 事件处理: 委托在事件处理中发挥了重要作用,它可以让你轻松地订阅和处理事件,使得 C# 中的事件模型更加强大和易用。

  3. 多播委托: 委托支持多播(Multicast)功能,允许将多个方法绑定到一个委托实例上,当委托被调用时,所有绑定的方法都会依次执行,这为事件处理和其他一些场景提供了便利。

  4. 解耦合: 使用委托可以实现方法的解耦合,使得代码更易于维护和理解,因为它可以将方法的定义和执行分开。

缺点

  1. 性能开销: 在调用委托时,会涉及一定的性能开销,因为它涉及额外的调用和间接引用。尤其在频繁调用的情况下,可能会对性能产生影响。

  2. 难以调试: 由于委托可以在运行时动态绑定方法,因此在调试过程中可能会增加一些困难,特别是当委托绑定的方法较多时,跟踪程序的执行流程可能会变得更加复杂。

  3. 内存管理: 委托使用的内存管理方式可能导致一些潜在的内存泄漏问题,特别是在多播委托的情况下,如果不正确地处理委托的生命周期,可能会导致对象无法被正确释放。

posted @ 2024-03-07 10:50  咸鱼翻身?  阅读(9)  评论(0编辑  收藏  举报