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)*/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#中 委托和事件的关系的更多相关文章
- c#中委托和事件(转)
C# 中的委托和事件 引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真 ...
- C#中委托和事件的区别实例解析
这篇文章主要介绍了C#中委托和事件的区别,并分别以实例形式展示了通过委托执行方法与通过事件执行方法,以及相关的执行流程与原理分析,需要的朋友可以参考下 本文实例分析了C#中委托和事件的区别,分享给大家 ...
- 转载:C#中委托、事件与Observer设计模式
原文地址 http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 感谢博主分享! 范例说明 假设 ...
- C#中委托和事件
目 录 将方法作为方法的参数 将方法绑定到委托 更好的封装性 限制类型能力 范例说明 Observer 设计模式简介 实现范例的Observer 设计模式 .NET 框架中的委托与事件 为什么委托定义 ...
- C#委托与事件的关系(转载)
1.C#中的事件和委托的作用?事件代表一个组件能够被关注的一种信号,比如你的大肠会向你发出想拉屎的信号,你就可以接收到上厕所.委托是可以把一个过程封装成变量进行传递并且执行的对象,比如你上蹲坑和上坐马 ...
- c#中委托和事件(续)(转)
本文将讨论委托和事件一些更为细节的问题,包括一些大家常问到的问题,以及事件访问器.异常处理.超时处理和异步方法调用等内容. 为什么要使用事件而不是委托变量? 在 C#中的委托和事件 中,我提出了两个为 ...
- C#中委托和事件的区别
大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法.事件可以被看作一个委托类型的变量,通过事件注册.取消多个委托或方法.本篇分别通过委托和事件执行多个方法,从中体会两者的区别. □ 通过委托 ...
- 关于c#中委托与事件的一些理解
文章目的:作者(初学者)在学习c#的过程中,对事件.委托及其中的“object sender,EventArgs e”一直感觉理解不透,因此在网上找了一些资料,学习并整理出了该篇笔记,希望能将自己的心 ...
- CS中委托与事件的使用-以Winform中跨窗体传值为例
场景 委托(Delegate) 委托是对存有某个方法的引用的一种引用类型变量. 委托特别用于实现事件和回调方法. 声明委托 public delegate int MyDelegate (string ...
随机推荐
- switch/catch
public class SwitchTest { public static void main(String[] args) { String param = null; switch (para ...
- Vmare虚拟机安装麻烦二三事
1).如何把虚拟机完整的迁移到另一个磁盘空间 2).VMware启动时提示我已移动或我已复制该虚拟机 3).虚拟机上显示主ip地址网络信息不可用怎么解决 4).vmware15虚拟机安装mac os ...
- 【VS开发】CString 转为 char *方法大全
[VS开发]CString 转为 char *方法大全 标签(空格分隔): [VS开发] 方法1: CString strTemp; char szTemp[128]; strTemp = _T(&q ...
- eNSP——VLAN基础配置和Access
原理: 早期的局域网技术是基于总线型的结构,也就是说所有主机共享一条传输线路.这就带来了很多问题:冲突域和安全问题.为了避免冲突域,我们使用二层交换机.但想想,一台计算机在总线上传输数据的时候,所有计 ...
- vue项目中的父子组件之间的传值。
首先说一下父子组件就是在一个vue文件中引入另一个vue文件,被引入vue文件就是子组件,引入vue文件的vue文件就是父组件.而在父组件中是不能直接调用子组件中的变量值的.下面详细说一下,父子组件之 ...
- 《MIT 6.828 Lab1: Booting a PC》实验报告
<MIT 6.828 Lab1: Booting a PC>实验报告 本实验的网站链接见:Lab 1: Booting a PC. 实验内容 熟悉x86汇编语言.QEMU x86仿真器.P ...
- 下载HTMLTestRunner 地址
通过pip安装 HTMLTestRunne失败 则需要通过手动下载. 下载地址: http://tungwaiyip.info/software/HTMLTestRunner.html 下载后,把H ...
- xpath的一些常用使用
xml文档<html> <head> <title>My page</title> </head> <body> <h2& ...
- taskverse学习
简介 taskverse是<linux二进制分析>一书作者编写的一个隐藏进程的检测工具,它使用/proc/kcore来访问内核内存,github的地址在这里:https://github. ...
- JS 验证字符串是否能转为json格式
var isJSON=function (str) { if (typeof str == 'string') { try { var obj = JSON.parse(str); if (typeo ...