C#中 委托和事件的关系

首先,委托 是一个好东西。按我的理解,委托 是针对 方法 的更小粒度的抽象。比较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 派生这些事情,都是为了 标准化。(也就是说,还存在非标准化的写法)关于标准化,后面再谈。

时间: 2024-12-17 20:10:27

C#中 委托和事件的关系的相关文章

c#中委托与事件的关系

C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:public delegate void MyEventHandler(object sender, MyEventArgs e);其中的两个参数,sender代表事件发送者,e是事件参数类.MyEventArgs类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生.当然,如果你的事件不含参数,那么可以直接用System.EventArgs类作为参数. 就是这么简单,结合delega

c#中委托和事件(转)

C# 中的委托和事件 引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里别(biè)得慌,混身不自在.本文中,我将通过两个范例由浅入深地讲述什么是委托.为什么要使用委托.事件的由来..Net Framework中的委托和事件.委托和事件对Observer设计模式的意义,对它们的中间代码也做了讨论. 将方法作为方法的参

委托与事件的关系

委托与事件的关系,可参照字段跟属性的关系来理解. 属性的一个功能就是设置字段,可以给字段设置权限.字段可以是值类型,也可以是引用类型,当然包括数组在内.那么委托呢?委托是把方法参数化,想要使用方法时直接使用就可以了(有时候要在不同的地方使用同一个方法,如果把方法的引用交给委托,要使用的时候直接让委托占位(委托去调用这个或这些方法),就方便多了). 声明一个委托:public deletate void MyDelegate(); 可以把委托当做一个容器(名片夹),里面装的是方法的引用(这里我们可

C#中委托和事件的区别实例解析

这篇文章主要介绍了C#中委托和事件的区别,并分别以实例形式展示了通过委托执行方法与通过事件执行方法,以及相关的执行流程与原理分析,需要的朋友可以参考下 本文实例分析了C#中委托和事件的区别,分享给大家供大家参考之用.具体如下: 大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法.事件可以被看作一个委托类型的变量,通过事件注册.取消多个委托或方法.本篇分别通过委托和事件执行多个方法,从中体会两者的区别. 一.通过委托执行方法 1 2 3 4 5 6 7 8 9 10 11 12 13 1

C#中委托和事件

目 录 1.1 理解委托 2 1.1.1 将方法作为方法的参数 2 1.1.2 将方法绑定到委托 4 1.2 事件的由来 6 1.2.1 更好的封装性 6 1.2.2 限制类型能力 9 1.3 委托的编译代码 10 1.4 .NET 框架中的委托和事件 11 1.4.1 范例说明 11 1.4.2 Observer 设计模式简介 12 1.4.3 实现范例的Observer 设计模式 13 1.4.4 .NET 框架中的委托与事件 14 1.5 委托进阶 16 1.5.1 为什么委托定义的返回值

C#中委托和事件的区别

大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法.事件可以被看作一个委托类型的变量,通过事件注册.取消多个委托或方法.本篇分别通过委托和事件执行多个方法,从中体会两者的区别. □ 通过委托执行方法 class Program { static void Main(string[] args) { Example example = new Example(); example.Go(); Console.ReadKey(); } } public class Example { pu

c#中委托和事件(续)(转)

本文将讨论委托和事件一些更为细节的问题,包括一些大家常问到的问题,以及事件访问器.异常处理.超时处理和异步方法调用等内容. 为什么要使用事件而不是委托变量? 在 C#中的委托和事件 中,我提出了两个为什么在类型中使用事件向外部提供方法注册,而不是直接使用委托变量的原因.主要是从封装性和易用性上去考虑,但是还漏掉了一点,事件应该由事件发布者触发,而不应该由客户端(客户程序)来触发.这句话是什么意思呢?请看下面的范例: NOTE:注意这里术语的变化,当我们单独谈论事件,我们说发布者(publishe

关于c#中委托与事件的一些理解

文章目的:作者(初学者)在学习c#的过程中,对事件.委托及其中的“object sender,EventArgs e”一直感觉理解不透,因此在网上找了一些资料,学习并整理出了该篇笔记,希望能将自己的心得记录下来作为积累.限于能力且是初学,有错误的地方还请大家批评指正. 注意:该笔记中有部分内容摘自网上的参考资料,并非作者原创,仅限学习交流,特此声明! 一. 委托与事件       委托与事件是一对相互关联的概念.委托是一种引用类型,可通过声明委托变量,并将其初始化为某个匹配的函数来实现对该函数的

C#中委托和事件的理解

看了张子阳博客后,总结了一下个人的理解,原文见:http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html 委托是可以把方法作为参数的一种东东. 把委托声明成与方法的返回值和参数一模一样. 然后把方法赋值给委托变量,委托变量就可以作为参数带入函数. 委托最主要的作用是实现C#中的事件. C#中的委托型事件需要遵守几个书写规范: 1.委托名称必须以EventHandler结尾.如:PlayEventHandler 2.委托必须声