事件(Event)委托与事件在同一页面

原文链接:https://www.cnblogs.com/ban-boi-making-dinner/p/18637475
同一个类(页面)里,你完全可以同时声明一个委托类型和一个同名事件,但必须满足两条语法铁律:
  1. 事件不能和委托类型同名——它们处于同一声明空间,C# 把类型名与成员名视为同一级符号表。
  2. 正确做法是:委托类型一个名字,事件再取另一个名字(习惯上加 Event 后缀或直接用系统泛型委托)。
    public delegate void ClickHandler(object sender, EventArgs e);
    public event ClickHandler Click;   // OK
    

      

    事件的定义与使用:

    事件是基于委托的封装,主要用于发布/订阅模式。它限制了对委托的直接访问,只允许通过特定的触发机制调用绑定的方法,从而增强了安全性。 以下是事件的实现示例:

    // 定义委托
    public delegate void AlarmHandler(string message);
    // 定义事件类
    public class Alarm
    {
       // 使用事件封装委托
       public event AlarmHandler OnAlarm;
       // 提供触发事件的方法
       public void TriggerAlarm(string message)
       {
           OnAlarm?.Invoke(message);
       }
    }
    class Program
    {
       static void Main(string[] args)
       {
           Alarm alarm = new Alarm();
           // 订阅事件
           alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器1:{msg}");
           alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器2:{msg}");
           // 触发事件
           alarm.TriggerAlarm("火灾警报!");
       }
    }
    

      输出: 警报处理器1:火灾警报!

                       警报处理器2:火灾警报!

    事件的安全性体现在以下几点: 外部代码只能通过 += 和 -= 操作符订阅或取消订阅事件。 无法直接调用事件的 Invoke 方法,触发事件只能通过类内部的方法。

    委托与事件的区别

    本质:委托是方法的集合,而事件是对委托的封装。

    调用方式:委托可以直接调用 Invoke 方法,而事件只能通过类的触发机制调用。

    外部访问:委托可以被外部代码随意修改或覆盖,而事件只能订阅或取消订阅,无法直接修改。

    应用场景:委托适用于灵活的直接调用场景,而事件适用于需要更高安全性和封装性的场景。

    总结

    在实际开发中,委托提供了灵活性,但容易引发安全问题;事件通过封装委托,避免了篡改和误用的风险。如果需要实现发布/订阅模式或对逻辑安全性要求较高,建议优先使用事件。

     

    风险示例:外部代码直接修改委托

    委托的开放性可能导致外部代码意外或恶意地修改其绑定逻辑。
    public class Program
    {
    static void Main()
    {
    Alarm alarm = new Alarm();

    // 正常绑定处理器
    alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器1:{msg}");
    alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器2:{msg}");

    // 正常触发
    alarm.OnAlarm?.Invoke("火灾警报!");

    // 外部代码恶意覆盖委托
    alarm.OnAlarm = (msg) => Console.WriteLine($"恶意代码:篡改了警报逻辑!");

    // 再次触发
    alarm.OnAlarm?.Invoke("火灾警报!");
    }
    }


    输出结果
    警报处理器1:火灾警报!
    警报处理器2:火灾警报!
    恶意代码:篡改了警报逻辑!


    分析:
    •外部代码可以直接覆盖委托的绑定内容(使用 = 操作符),导致原有功能被破坏。
    •在复杂系统中,这种行为可能引发严重的逻辑漏洞。


    三、事件的安全性与触发机制

    事件的使用

    事件是基于委托的封装,限制了外部对委托的访问权限,从而增强安全性。
    public delegate void AlarmHandler(string message);

    public class Alarm
    {
    // 使用事件代替公开的委托
    public event AlarmHandler OnAlarm;

    // 提供触发事件的方法
    public void TriggerAlarm(string message)
    {
    OnAlarm?.Invoke(message);
    }
    }

    public class Program
    {
    static void Main()
    {
    Alarm alarm = new Alarm();

    // 绑定事件处理器
    alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器1:{msg}");
    alarm.OnAlarm += (msg) => Console.WriteLine($"警报处理器2:{msg}");

    // 通过触发方法调用事件
    alarm.TriggerAlarm("火灾警报!");
    }
    }


    输出结果
    警报处理器1:火灾警报!
    警报处理器2:火灾警报!


    事件的安全机制

    1.访问限制:
    •外部代码只能通过 += 和 -= 操作符订阅或取消订阅事件。
    •无法直接调用事件的 Invoke 方法,触发事件只能通过类内部的触发方法。


    2.防止篡改:
    •外部代码无法使用 = 覆盖事件绑定的逻辑,避免了逻辑被意外或恶意篡改。


    尝试修改事件的代码(编译错误)
    // 尝试直接覆盖事件
    alarm.OnAlarm = (msg) => Console.WriteLine($"恶意代码:篡改了警报逻辑!"); // 编译错误

    // 尝试直接触发事件
    alarm.OnAlarm?.Invoke("火灾警报!"); // 编译错误

     

    四、委托与事件的对比总结


    特性

    委托

    事件


    本质 方法的集合 委托的封装
    调用方式 可以直接调用 Invoke 方法 只能通过类的触发机制调用
    外部代码访问 可随意修改、覆盖 只能订阅或取消订阅,无法直接修改
    应用场景 灵活的直接调用场景 需要更高安全性和封装性的场景


    五、如何选择

    1.选择委托的场景:
    •方法调用需要高灵活性,允许直接触发。
    •内部逻辑简单,没有安全性和访问限制的需求。


    2.选择事件的场景:
    •需要实现发布/订阅模式。
    •需要严格限制外部代码对绑定逻辑的访问和触发权限。

     

    六、委托与事件的安全性思考

    通过本文的示例,你会发现,委托与事件最大的区别在于对安全性的设计取舍。委托提供了灵活性,但容易引发安全问题;事件则通过封装委托,避免了篡改和误用的风险。

    在实际开发中,如果涉及多个订阅者的逻辑,且对逻辑安全性要求较高,请优先使用事件。理解这一点,不仅能帮助你写出更安全的代码,也能让你在团队协作中避免潜在问题。

    希望本文让你对委托与事件的本质有了更深入的理解!

posted @ 2025-10-11 16:29  yinghualeihenmei  阅读(4)  评论(0)    收藏  举报