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 ...
随机推荐
- java中length和length()的区别?
在java中String类可以定义字符串变量和字符串数组,length()用于求String字符串对象的长度,而length用 于求String字符串数组的长度. length()是求String ...
- thinkPHP5 类库包注册
tp5的类库包注册分为自动注册和手动注册 自动注册 我们只需要把自己的类库包目录放入EXTEND_PATH目录(默认为extend,可配置),就可以自动注册对应的命名空间,例如: 我们在extend目 ...
- apue-ubuntu环境搭建
目录 apue环境搭建 title: apue环境搭建 date: 2019/11/19 19:25:18 toc: true --- apue环境搭建 下载编译 wget http://www.ap ...
- php 求两个数组的差集应该注意的事情
对于 phper 来说 array_diff 这个函数应该知道它的用途,获取两个数组的差集,我理解中的差集是这样的 但是执行下代码会发现结果并不是 <?php $a = [1,2,3,4,5]; ...
- 嵌入式linux第一阶段笔记
1.虚拟网络编辑器(vm):三种模式:(VMnet0)桥接模式(vm和windows公用同个网络(同个物理端口)),(VMnet1)仅主机模式,(VMnet8)NAT模式(vm连接一个虚拟的路由(WA ...
- 用Lua的协程实现类似Unity协程的语句块
local co_time_tbl = {} setmetatable(co_time_tbl, { __len = function(o) for k, v in pairs(o) do count ...
- Spring集成kafka,消费者运行时内存占用会一直增长
Spring集成kafka,消费者运行时内存占用会一直增长? 20C 本人用Spring集成kafka消费者,发布运行时内存占用会一直升高,最后程序挂掉.请各位大神看看,提供解决方法 以下是我的配置文 ...
- 【css】浅谈BFC
定义: 块格式化上下文(Block Formatting Context,BFC) 是Web页面的可视化CSS渲染的一部分,是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域. BFC的布 ...
- Java Web-EL表达式 in JSP
Java Web-EL表达式 in JSP 概念 EL(Expression Language)是一种表达式语言,可以替换和简化JSP页面上JAVA代码的书写 语法 ${<在这里写表达式> ...
- netty 自定义协议
netty 自定义协议 netty 是什么呢? 相信很多人都被人问过这个问题.如果快速准确的回复这个问题呢?网络编程框架,netty可以让你快速和简单的开发出一个高性能的网络应用.netty是一个网络 ...