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 ...
随机推荐
- 洛谷 题解 P2280 【[HNOI2003]激光炸弹】
一道很好的二维前缀和模板题. 什么是二维前缀和? 从这张图可以看出前缀和的求法: Map[i][j]=Map[i-1][j]+Map[i][j-1]-Map[i-1][j-1]+Map[i][j]; ...
- 《C程序设计语言》学习笔记
1. 在C程序中,如果字符串过长而需要跨行时,要在换行时加上“\”. printf("Hello, world"); // error printf("Hello, wo ...
- String、StringBuilder、StringBuffer的爱恨情仇
第三阶段 JAVA常见对象的学习 StringBuffer和StringBuilder类 (一) StringBuffer类的概述 (1) 基本概述 下文以StringBuffer为例 前面我们用字符 ...
- js轮播图和bootstrap中的轮播图
js中的轮播图案例: <!DOCTYPE html><html lang="en"> <head> <meta charset=" ...
- FastJson 介绍
Json详解 Json是一种轻量级的数据交换格式,采用一种“键:值”对的文本格式来存储和表示数据,在系统交换数据过程中常常被使用,是一种理想的数据交换语言.在使用 Java 做 Web 开发时,不可避 ...
- 【Jmeter源码解读】002——程序入口类NewDriver.java
1.运行环境的检查 2.刚才初始化的 classloader 加载类 org.apache.jmeter.JMeter 然后通过 java 反射的方式来调用 org.apache.jmeter.JMe ...
- 【AtCoder】AGC007
AGC007 A - Shik and Stone 如果i + j走过的格子只有一个,那么就是可以走到 #include <bits/stdc++.h> #define fi first ...
- StorageClass-动态PVC
StorageClass 之前我们部署了PV 和 PVC 的使用方法,但是前面的 PV 都是静态的,什么意思?就是我要使用的一个 PVC 的话就必须手动去创建一个 PV,我们也说过这种方式在很大程度上 ...
- Angular 表单验证类库 ngx-validator 1.0 正式发布
背景介绍 之前写了一篇 <如何优雅的使用 Angular 表单验证>,结尾处介绍了统一验证反馈的类库 ngx-validator ,由于这段时间一直在新模块做微前端以及相关业务组件库, ...
- LocalDate与Date转化
// 01. java.util.Date --> java.time.LocalDateTimepublic void UDateToLocalDateTime() { java.util.D ...