首先,委托 是一个好东西。按我的理解,委托 是针对 方法 的更小粒度的抽象。比较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)*/old);
}
else
{
Console.WriteLine("价格降幅:{0}%", (old - now)*/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 = ;
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()[].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)*/old);
}
else
{
Console.WriteLine("价格降幅:{0}%", (old - now)*/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 派生这些事情,都是为了 标准化。(也就是说,还存在非标准化的写法)关于标准化,后面再谈。

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

  1. c#中委托和事件(转)

    C# 中的委托和事件 引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真 ...

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

    这篇文章主要介绍了C#中委托和事件的区别,并分别以实例形式展示了通过委托执行方法与通过事件执行方法,以及相关的执行流程与原理分析,需要的朋友可以参考下 本文实例分析了C#中委托和事件的区别,分享给大家 ...

  3. 转载:C#中委托、事件与Observer设计模式

    原文地址 http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 感谢博主分享! 范例说明 假设 ...

  4. C#中委托和事件

    目 录 将方法作为方法的参数 将方法绑定到委托 更好的封装性 限制类型能力 范例说明 Observer 设计模式简介 实现范例的Observer 设计模式 .NET 框架中的委托与事件 为什么委托定义 ...

  5. C#委托与事件的关系(转载)

    1.C#中的事件和委托的作用?事件代表一个组件能够被关注的一种信号,比如你的大肠会向你发出想拉屎的信号,你就可以接收到上厕所.委托是可以把一个过程封装成变量进行传递并且执行的对象,比如你上蹲坑和上坐马 ...

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

    本文将讨论委托和事件一些更为细节的问题,包括一些大家常问到的问题,以及事件访问器.异常处理.超时处理和异步方法调用等内容. 为什么要使用事件而不是委托变量? 在 C#中的委托和事件 中,我提出了两个为 ...

  7. C#中委托和事件的区别

    大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法.事件可以被看作一个委托类型的变量,通过事件注册.取消多个委托或方法.本篇分别通过委托和事件执行多个方法,从中体会两者的区别. □ 通过委托 ...

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

    文章目的:作者(初学者)在学习c#的过程中,对事件.委托及其中的“object sender,EventArgs e”一直感觉理解不透,因此在网上找了一些资料,学习并整理出了该篇笔记,希望能将自己的心 ...

  9. CS中委托与事件的使用-以Winform中跨窗体传值为例

    场景 委托(Delegate) 委托是对存有某个方法的引用的一种引用类型变量. 委托特别用于实现事件和回调方法. 声明委托 public delegate int MyDelegate (string ...

随机推荐

  1. The request was rejected because the URL contained a potentially malicious String ";"报错解决

    报错信息 浏览器中看到的报错 错误摘要: The request was rejected because the URL contained a potentially malicious Stri ...

  2. Ubuntu中 apt-get -f install 命令

    Ubuntu 下当发现可能是安装的其他软件包不兼容导致了安装包出错时,可以根据提示需要执行“sudo apt-get  -f install ”来卸载之前的冲突包. 如果安装过aptitude包,还可 ...

  3. Guava源码阅读-base-CharMatcher

    package com.google.common.base; (部分内容摘自:http://blog.csdn.net/idealemail/article/details/53860439) 之前 ...

  4. kafka producer interceptor拦截器(五)

    producer在发送数据时,会经过拦截器和序列化,最后到达相应的分区.在经过拦截器时,我们可以对发送的数据做进步的处理. 要正确的使用拦截器需要以下步骤: 1.实现拦截器ProducerInterc ...

  5. 【转贴】SMP、NUMA、MPP体系结构介绍

    SMP.NUMA.MPP体系结构介绍 https://www.cnblogs.com/tcicy/p/10185783.html 从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构 ...

  6. 1、Ubuntu linux下同步windows火狐foxfire 浏览器收藏夹问题

    最近在ubuntu系统中使用自带的firefox浏览器,发现有一些问题,比如登陆后,书签,历史记录等,原本在windows下同步的数据无法同步,添加书签的功能也无法使用. 经过查询资料后得知,unbu ...

  7. rabbitmq消息队列,消息发送失败,消息持久化,消费者处理失败相关

    转:https://blog.csdn.net/u014373554/article/details/92686063 项目是使用springboot项目开发的,前是代码实现,后面有分析发送消息失败. ...

  8. Delphi编译器属性(特别修饰符Ref,Unsafe,Volatile,Weak)

    1 Refdelphi中常量参数就像一个本地常量,或者说只读变量.常量参数与值参数类似,除了不能在过程或函数体内给常量参数赋值,并且不能将常量参数传给另一个var类型参数.(但是,如果你常量参数传递的 ...

  9. 并不对劲的CSP-S2019

    day1 对题的第一印象: t1:颇有"小凯的疑惑"之风(赛后发现确实如此,因为最好写的正解也可以直接输出) t2:log方会被卡吧?好像倍增一个log?(赛后发现有很好写的线性做 ...

  10. hdu 1671 复习字典树

    #include<cstdio> #include<iostream> #include<string> #include<cstdlib> #defi ...