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 ...
随机推荐
- The request was rejected because the URL contained a potentially malicious String ";"报错解决
报错信息 浏览器中看到的报错 错误摘要: The request was rejected because the URL contained a potentially malicious Stri ...
- Ubuntu中 apt-get -f install 命令
Ubuntu 下当发现可能是安装的其他软件包不兼容导致了安装包出错时,可以根据提示需要执行“sudo apt-get -f install ”来卸载之前的冲突包. 如果安装过aptitude包,还可 ...
- Guava源码阅读-base-CharMatcher
package com.google.common.base; (部分内容摘自:http://blog.csdn.net/idealemail/article/details/53860439) 之前 ...
- kafka producer interceptor拦截器(五)
producer在发送数据时,会经过拦截器和序列化,最后到达相应的分区.在经过拦截器时,我们可以对发送的数据做进步的处理. 要正确的使用拦截器需要以下步骤: 1.实现拦截器ProducerInterc ...
- 【转贴】SMP、NUMA、MPP体系结构介绍
SMP.NUMA.MPP体系结构介绍 https://www.cnblogs.com/tcicy/p/10185783.html 从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构 ...
- 1、Ubuntu linux下同步windows火狐foxfire 浏览器收藏夹问题
最近在ubuntu系统中使用自带的firefox浏览器,发现有一些问题,比如登陆后,书签,历史记录等,原本在windows下同步的数据无法同步,添加书签的功能也无法使用. 经过查询资料后得知,unbu ...
- rabbitmq消息队列,消息发送失败,消息持久化,消费者处理失败相关
转:https://blog.csdn.net/u014373554/article/details/92686063 项目是使用springboot项目开发的,前是代码实现,后面有分析发送消息失败. ...
- Delphi编译器属性(特别修饰符Ref,Unsafe,Volatile,Weak)
1 Refdelphi中常量参数就像一个本地常量,或者说只读变量.常量参数与值参数类似,除了不能在过程或函数体内给常量参数赋值,并且不能将常量参数传给另一个var类型参数.(但是,如果你常量参数传递的 ...
- 并不对劲的CSP-S2019
day1 对题的第一印象: t1:颇有"小凯的疑惑"之风(赛后发现确实如此,因为最好写的正解也可以直接输出) t2:log方会被卡吧?好像倍增一个log?(赛后发现有很好写的线性做 ...
- hdu 1671 复习字典树
#include<cstdio> #include<iostream> #include<string> #include<cstdlib> #defi ...