C#中委托和事件的区别

本文探讨了C#中委托和事件的概念及其差异。事件是委托的封装,提供了发布/订阅机制,防止误操作导致委托链断裂,并限制了触发事件的权限。委托可在类内外调用,而事件只能在声明类的内部触发,确保安全性。文章通过不同版本的观察者模式示例,展示了委托和事件的使用,并提出通常在不需要外部触发时,应将委托转换为事件以增强代码健壮性。

委托提供与C++中“函数指针”相同的功能,用于传递和调用函数的引用,是观察者模式的一种实现。

事件是用委托实现的,是对委托的额外封装,其本质上是一种特殊的委托。

事件是基于委托的,为委托提供了一个发布/订阅机制。可以说事件是一种特殊的委托,他的调用和委托是一样的。


事件的作用

  • 封装订阅: 事件将委托的订阅操作进行封装,仅允许 += 和 -= 操作,避免程序员在开发时因误用 = 使得委托链断裂
  • 封装发布: 事件确保只有包含它类才能触发事件通知,杜绝在委托中出现的“订阅者”也能触发

委托实例的调用可以在声明委托的类的内部和外部调用(不安全)。  

事件对象的调用只能在声明事件的类的内部调用(安全)。(事件和委托的使用代码几乎完全相同)


事件的声明

public event 委托类型 事件名称

通常事件的命名以事件名称+Event来命名。如public event delegate NotifyEvent;

事件和委托的区别如下:

事件只能在方法的外部进行声明,而委在方法的外部和内部都可以声明。

事件只能在类的内部触发,不能在类的外部触发。而委托在类的内部和外都都可以触发。

委托一般用于回调,而事件用于外部接口。例如在观察者模式中,在被观察者中可以声明一个事件作为外部观察者注册的接口。

同时,这个事件只能在被观察者内部触发,而观察者中无法触发该事件,从而保证了安全性。


本篇使用分别使用委托和事件来实现简单的观察者模式例子,三个版本输出完全相同,为方便对比,使用了最原始的delegate语法。读者可以对比三版的不同之处来了解两者的区别。

委托版本

using System;
 
namespace LearningDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            //出版社有一本叫《故事会》的杂志
            Publisher publisher = new Publisher("《故事会》");
 
            //读者小A订了这本杂志
            Observer observerA = new Observer("小A");
            publisher.Magazine += observerA.RecieveMagazine;
 
            //读者小B也订了这本杂志
            Observer observerB = new Observer("小B");
            publisher.Magazine += observerB.RecieveMagazine;
 
            //出版社印刷本月的《故事会》
            publisher.PublishingMagazine();
 
            Console.ReadLine();
        }
    }
 
    //读者
    class Observer
    {
        public Observer(string _name)
        {
            name = _name;
        }
        public string name;
 
        public void RecieveMagazine(string message)
        {
            Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, message);
        }
    }
 
    //出版社
    class Publisher
    {
        public Publisher(string magName)
        {
            magazineName = magName;
        }
 
        public string magazineName;
 
        public delegate void MagazineDelegate(string message);
        public MagazineDelegate Magazine;
 
        public void PublishingMagazine()
        {
            //如果没人订,就不用印了
            //此处必须判断Delegate对象是否为空,调用空的Delegate对象会引发异常
            if (Magazine != null)
            {
                Magazine(magazineName);
            }
        }
    }
}

委托有一个特点:由于委托的订阅和触发都直接作用于delegate对象,这导致委托可以在可订阅的空间中被触发,也就是说我们无法将委托的触发封装起来。而事件event对象只能在其定义的类中被触发。


一个最最简单的事件版本

using System;
 
namespace LearningDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            //出版社有一本叫《故事会》的杂志
            Publisher publisher = new Publisher("《故事会》");
 
            //读者小A订了这本杂志
            Observer observerA = new Observer("小A");
            publisher.Magazine += observerA.RecieveMagazine;
 
            //读者小B也订了这本杂志
            Observer observerB = new Observer("小B");
            publisher.Magazine += observerB.RecieveMagazine;
 
            //出版社印刷本月的《故事会》
            publisher.PublishingMagazine();
 
            Console.ReadLine();
        }
 
    }
 
    //读者
    class Observer
    {
        public Observer(string _name)
        {
            name = _name;
        }
        public string name;
 
        public void RecieveMagazine(string message)
        {
            Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, message);
        }
    }
 
    //出版社
    class Publisher
    {
        public Publisher(string megName)
        {
            magazineName = megName;
        }
 
        public string magazineName;
 
        public delegate void MagazineDelegate(string message);
        //使用自定义的委托类型和event关键字创建事件对象
        public event MagazineDelegate Magazine;
 
        public void PublishingMagazine()
        {
            //如果没人订,就不用印了
            //此处必须判断事件对象是否为空
            if (Magazine != null)
            {
                Magazine(magazineName);
            }
        }
    }
}

这个最最简单的事件,就是给原有的委托加了一层event关键字的封装,增加了上述的两个特性。这个简单的例子只能用于了解委托和事件语法的差别,下面给出标准的事件语法的版本。


标准的事件版本

using System;
 
namespace LearningDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            //出版社有一本叫《故事会》的杂志
            Publisher publisher = new Publisher("《故事会》");
 
            //读者小A订了这本杂志
            Observer observerA = new Observer("小A");
            publisher.Magazine += observerA.RecieveMagazine;
 
            //读者小B也订了这本杂志
            Observer observerB = new Observer("小B");
            publisher.Magazine += observerB.RecieveMagazine;
 
            //出版社印刷本月的《故事会》
            publisher.PublishingMagazine();
 
            Console.ReadLine();
        }
    }
 
    //读者
    class Observer
    {
        public Observer(string _name)
        {
            name = _name;
        }
        public string name;
 
        //接受信息的函数要与Event的格式保持一致,输入一个object对象和Event消息类
        public void RecieveMagazine(object sender, Publisher.MagazineMessage magazineMessage)
        {
            Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, magazineMessage.Message);
        }
    }
 
    //出版社
    class Publisher
    {
        //事件传递的消息必须封装到一个类中,该类必须继承EventArgs类
        public class MagazineMessage : EventArgs
        {
            public MagazineMessage(string mes)
            {
                message = mes;
            }
 
            private string message;
 
            public string Message { get => message; set => message = value; }
        }
 
        public Publisher(string megName)
        {
            magazineName = megName;
        }
        public string magazineName;
 
        //定义Event
        public event EventHandler<MagazineMessage> Magazine;
        //EventHandler<MagazineMessage> 的原型是一个泛型委托:delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
 
 
        //调用Event,Event只能在自己定义类中被触发调用
        public void PublishingMagazine()
        {
            //与Delegate一样,此处必须判断event对象是否为空,调用空的Event/Delegate对象会引发异常
            if (Magazine != null)
            {
                Magazine(this, new MagazineMessage(magazineName));
            }
        }
    }
}

什么时候用委托?什么时候用事件?(个人观点)

由上可知,从语法上,我们可以使用event关键字将任意委托转化为事件。那么简而言之,出于代码健壮性考虑,如果一个委托不需要在其定义的类之外进行触发,那就将其转化为事件,保证它不会在不可知的情况下被触发。


参考:

[C#基础 理解] 简述委托与事件的区别

C# 事件和委托的区别

C# 事件,委托与事件的区别

C#中委托和事件的区别


 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独立游戏开发指南

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值