• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C#中委托和事件的关系

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

首先,委托 是一个好东西。按我的理解,委托 是针对 方法 的更小粒度的抽象。比较interface,他精简了一些代码。使得 订阅-通知 (观察者模式)的实现变得非常简洁。

关于事件,我最初的理解是:事件是利用委托  对  通知-订阅模式 的一种实现方式。

我觉得我并没有理解错,但还不够精确

我现在要问

为什么要用非要事件来实现 通知-订阅模式? 而不直接用委托呢?事件到底解决了什么问题?

在《果壳中的C# 中文版》 P112页 说了。

  • 总的目标是 事件-订阅 模式中,保护订阅互不影响。

如何理解这句话呢?

先看一个例子,我们不使用事件,如何实现一个订阅模式。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 事件
{
    delegate void PriceChangeHandler(decimal oldPrice,decimal newPrice);
    class Stock
    {
        private decimal price;
        public PriceChangeHandler PriceChanged;

        public Stock(decimal price)
        {
            this.price = price;
        }

        public decimal Price
        {
            get { return price; }
            set
            {
                decimal oldPrice = price;
                price = value;
                if (PriceChanged != null && price != oldPrice)
                {
                    PriceChanged(oldPrice,price);    
                }

            }
        }
    }

    class Department1
    {
        public void PriceChangeEvent(decimal old, decimal now)
        {
            if (old < now)
            {
                Console.WriteLine("价格上涨:{0}", now - old);
            }
            else
            {
                Console.WriteLine("价格下降:{0}", old - now);
            }
        }
    }

    class Department2
    {
        public void PriceChangeEvent(decimal old, decimal now)
        {
            if (old < now)
            {
                Console.WriteLine("价格涨幅:{0}%", (now - old)*100/old);
            }
            else
            {
                Console.WriteLine("价格降幅:{0}%", (old - now)*100/old);
            }
        }    
    }

    class p
    {
        public static void Main(string[] args)
        {
            Stock stock = new Stock(10.0m);
            Department1 d1 = new Department1();
            Department2 d2 = new Department2();
            stock.PriceChanged += d1.PriceChangeEvent;
            stock.PriceChanged += d2.PriceChangeEvent;
            stock.Price = 100;
            Console.ReadKey();
        }   
    }

}

上例中,库存的价格一旦变化就通知 部门1,部门2,部门1关心价格变化,部门2关心涨幅。这个例子使用了委托,实现 通知-订阅 模式。看起来没有问题。

但是,我们可以这样修改Main中的代码。

 

public static void Main(string[] args)
        {
            Stock stock = new Stock(10.0m);
            Department1 d1 = new Department1();
            Department2 d2 = new Department2();
            stock.PriceChanged += d1.PriceChangeEvent;
            stock.PriceChanged += d2.PriceChangeEvent;
            stock.Price = 100m;

            stock.PriceChanged = d1.PriceChangeEvent; //问题1,重新指定了订阅者,导致d2订阅丢失了!
            stock.Price = 90m;

            stock.PriceChanged = null;                //问题2,外部代码可以清除订阅者。
            stock.Price = 80m;                        

            stock.PriceChanged += d1.PriceChangeEvent;
            stock.PriceChanged += d2.PriceChangeEvent;
            stock.PriceChanged.GetInvocationList()[1].DynamicInvoke(70m,10m); //问题3,外部可以这样不通过改变stock.Prince,来间接影响订阅者。
            

            Console.ReadKey();
        }   

 

显然,外部代码通过这些写法,影响了调阅。违反 “保护订阅互不影响

看起来,我们需要实现一种机制,达到保护 通知类 (本例中的Stock)中的 委托,

1,不能使用 = 符号来 改变通知对象,只能用 += -= 来订阅,退订。

2,不能让 委托指向 null

3,不能访问到委托内部的调用链(即GetInvocationList()

4,目标是让 这个委托,纯粹的变成一个容器。拒绝外部的一切干扰。

 

.net 设计者给出的方案是这样的,提供了一个叫做 event访问器的东西。可以将委托进行包装,是其满足上面的3个约束。(本质就是通过增加编译器关键字,达到约束委托的母的)

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace 事件
{
    delegate void PriceChangeHandler(decimal oldPrice,decimal newPrice);
    class Stock
    {
        private decimal price;
        
        //public PriceChangeHandler PriceChanged;  --原先的注释调用,对比下面的代码

        private PriceChangeHandler priceChanged;    //1 ,将委托改为私有的。
        public event PriceChangeHandler PriceChanged //2 ,类似于属性访问器。这叫事件访问器。
        {
            add { priceChanged += value; }          //add 订阅
            remove { priceChanged -= value; }       //remove 退订
        }

        public Stock(decimal price)
        {
            this.price = price;
        }

        public decimal Price
        {
            get { return price; }
            set
            {
                decimal oldPrice = price;
                price = value;
                if (PriceChanged != null && price != oldPrice)
                {
                    PriceChanged(oldPrice,price);    
                }

            }
        }
    }

    class Department1
    {
        public void PriceChangeEvent(decimal old, decimal now)
        {
            if (old < now)
            {
                Console.WriteLine("价格上涨:{0}", now - old);
            }
            else
            {
                Console.WriteLine("价格下降:{0}", old - now);
            }
        }
    }

    class Department2
    {
        public void PriceChangeEvent(decimal old, decimal now)
        {
            if (old < now)
            {
                Console.WriteLine("价格涨幅:{0}%", (now - old)*100/old);
            }
            else
            {
                Console.WriteLine("价格降幅:{0}%", (old - now)*100/old);
            }
        }    
    }

    class p
    {
        public static void Main(string[] args)
        {
            Stock stock = new Stock(10.0m);
            Department1 d1 = new Department1();
            Department2 d2 = new Department2();
            stock.PriceChanged += d1.PriceChangeEvent;
            stock.PriceChanged += d2.PriceChangeEvent;
            stock.Price = 100m;

            /** 现在这段代码无法编译
            stock.PriceChanged = d1.PriceChangeEvent; //问题1,重新指定了订阅者,导致d2订阅丢失了!
            stock.Price = 90m;
            **/

            /** 这段无法编译了
            stock.PriceChanged = null;                //问题2,外部代码可以清除订阅者。
            stock.Price = 80m;                        
            **/
            stock.PriceChanged += d1.PriceChangeEvent;
            stock.PriceChanged += d2.PriceChangeEvent;

            /** 这段也无法编译了
            stock.PriceChanged.GetInvocationList()[1].DynamicInvoke(70m,10m); //问题3,外部可以这样不通过改变stock.Prince,来间接影响订阅者。
            **/

            Console.ReadKey();
        }   
    }

}

这样通过 事件访问器,达到保护了委托的目的。让 通知-订阅  模式  变得健壮,只能使用 += -= 2个方法来 订阅,退订,其他的外部访问一律无法编译。

然后,.net设计者,觉得写这么多的代码来实现事件访问器太麻烦,就加了语法糖进行简化。

 

class Stock
    {
        private decimal price;
        
        //public PriceChangeHandler PriceChanged;  --原先的注释调用,对比下面的代码
        
        /*** 这样写事件访问器太麻烦。
        private PriceChangeHandler priceChanged;    //1 ,将委托改为私有的。
        public event PriceChangeHandler PriceChanged //2 ,类似于属性访问器。这叫事件访问器。
        {
            add { priceChanged += value; }          //add 订阅
            remove { priceChanged -= value; }       //remove 退订
        }*/

        public event PriceChangeHandler PriceChanged;

        public Stock(decimal price)
        {
            this.price = price;
        }

        public decimal Price
        {
            get { return price; }
            set
            {
                decimal oldPrice = price;
                price = value;
                if (PriceChanged != null && price != oldPrice)
                {
                    PriceChanged(oldPrice,price);    
                }

            }
        }
    }

于是,就类似属性访问器一样,简化代码变成了,现在这样声明事件的方式。

 

总结,

事件是 利用 委托  实现 发布-订阅 模式的 方式。

他比 单纯用委托 健壮。(特指在发布-订阅 这个模式中,牺牲灵活性是必然的。)

 

至于,从System.EventArgs 派生这些事情,都是为了 标准化。(也就是说,还存在非标准化的写法)关于标准化,后面再谈。

 


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
利用C# + GDI plus模拟杂乱无章的现实场景发布时间:2022-07-14
下一篇:
C#设置DataGrid的列宽发布时间:2022-07-14
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap