# 委托与事件
# 定义
  • 委托:一种引用类型,表示具有特定参数列表和返回类型的方法的引用

  • 委托对象包含什么?

    方法指针:指向当前委托指向方法的内存地址
    对象:委托指向方法属于哪个对象的方法,如果是静态方法,则为 null
    委托数据(委托链):用于多播委托存放委托的引用的数组

    在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。你可以通过委托实例调用方法。委托用于将方法作为参数传递给其他方法。事件处理程序就是通过委托调用的方法

  • 事件:类或对象可以通过事件向其他类或对象通知发生的相关事情。发送(或引发)事件的类称为 “发布者”,接收(或处理)事件的类称为 “订阅者”。

# 参考代码
# 委托
public class DelegateDemo
{
    delegate void 反编译委托();
    public void 测试()
    {
        反编译委托 委托实例 = new 反编译委托(方法1);
        委托实例 += 方法2;
        委托实例();
    }
    public void 方法1() { }
    public void 方法2() { }
}

写汉字反编译的时候更加清晰点

反编译之后的代码

delegate

委托反编译实际上是创建了一个密封类,里面有 Invoke,BeginInvoke 和 EndInvoke 方法。其中 Invoke 是同步执行委托指向的方法,BeginInvoke 和 EndInvoke 方法是异步执行。

# 事件

示例代码

/// <summary>
/// 事件参数
/// </summary>
public class CustomEventArgs : EventArgs
{
    public CustomEventArgs(string message)
    {
        Message = message;
    }
    public string Message { get; set; }
}
/// <summary>
/// 事件发布者
/// </summary>
class Publishe1r
{
    // 用系统自带的泛型委托创建一个事件
    public event EventHandler<CustomEventArgs> RaiseCustomEvent;
    public void DoSomething()
    {
        // 引发事件
        OnRaiseCustomEvent(new CustomEventArgs("Event triggered"));
    }
    /// <summary>
    /// 将事件调用包装在受保护的虚拟方法中  以允许派生类重写事件调用行为
    /// </summary>
    /// <param name="e"></param>
    protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
    {
        // 如果最后一个订阅者在 null 检查之后和引发事件之前立即取消订阅,则制作事件的临时副本以避免出现竞争条件的可能性
        EventHandler<CustomEventArgs> raiseEvent = RaiseCustomEvent;
        // 没有订阅者事件将是 null
        if (raiseEvent != null)
        {
            e.Message += $" at {DateTime.Now}";
            raiseEvent(this, e);
        }
    }
}
/// <summary>
/// 事件订阅者
/// </summary>
class Subscriber
{
    private readonly string _id;
    public Subscriber(string id, Publisher pub)
    {
        _id = id;
        // 订阅事件
        pub.RaiseCustomEvent += HandleCustomEvent;
    }
    // 定义引发事件时要采取的操作 
    void HandleCustomEvent(object sender, CustomEventArgs e)
    {
        try
        {
            if (_id == "sub1")
            {
                //  try catch 后  事件抛出异常了 也不会影响下面的订阅者继续执行 
                throw new Exception("sub1 exceptoin");
            }
            Console.WriteLine($"{_id} received this message: {e.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

事件当中有三个角色,事件参数,事件发布者,事件订阅者,发布者和订阅者通过委托进行沟通

事件发布者 PublisherIL 反编译如下图所示:

delegate

IL 代码中可以看到 event 事件也是编译成类,另外生成了一个私有委托字段,还有两个方法,add_RiseCustomEvent 和 remove_RiseCustomEvent

补充:

事实上事件其实是对委托的一种包装,事件包含add和remove两个方法,其本质是对委托的Combine和Remove两个方法

委托可以作为一种约束条件来约束事件

  • 事件的触发是能用事件的发布者来操作,在事件发布者的外部不能调用 Invoke 方法主动触发事件

为什么要用委托来声明事件?

  • 事件与委托正如字段与属性一样,用属性将字段进行封装,属性的 get/set 方法约束字段是否可以被外部设置和获取字段值,同样事件也是一样,事件封装了委托内部的 Invoke 方法,防止外部主动执行委托,而是只能由事件的发布者触发
# 总结
# 委托的作用
  • 委托可以用于调用方法
  • 委托用于将方法作为参数传递给其他方法
  • 委托可以作为一种约束,限制方法的调用 (也就是事件)
# 委托与事件的区别
  • 事件只能在方法的外部进行声明,而委托在方法的外部和内部都可以进行声明
  • 事件只能在类的内部进行触发,不能在类的外部进行触发。而委托在类的内部和外部都可触发;
  • 委托一般用于回调,而事件一般用于外部接口。在观察者模式中,被观察者可在内部声明一个事件作为外部观察者注册的接口

参考连接

知乎链接参考

更新于