C#委托浅析与漫谈
作者:网络转载 发布时间:[ 2016/4/18 13:49:22 ] 推荐标签:测试开发技术 C#
Downloader.cs
class Downloader
{
public void Download(string resource)
{
for (int i = 1, size = 10; i this.OnProrgess(i, size);
Thread.Sleep(500);
}
}
public void AddMonitor(IProgressMonitor monitor)
{
this._monitors.Add(monitor);
}
public void RemoveMonitor(IProgressMonitor monitor)
{
this._monitors.Remove(monitor);
}
private void OnProrgess(int done, int total)
{
foreach (var i in this._monitors)
{
i.OnProgress(done, total);
}
}
private ICollection _monitors = new List();
//进度监视者集合
}
Program.cs
class Program
{
static void Main(string[] args)
{
var resouce = "visual studio.iso";
var downloader = new Downloader();
downloader.AddMonitor(new ProgressVeiw());
Console.WriteLine("正在下载 " + resouce);
downloader.Download(resouce);
Console.ReadKey();
}
}
整个逻辑是这样的:
创建下载器
ProgressView对下载进度“感兴趣”,到下载器那“登记一下”
下载器开始下载
下载器下载过程进度有变化时通知已登记了的对象,具体这些对象要干嘛,它不管,它只负责通知
ProgressView收到通知,更新界面
从代码中可以看到,实现“登记”、“通知”是通过手动操作一个集合实现的,其实这种功能.Net已经帮我们实现,那是多播委托。
3.1 多播委托(MulticastDelegate)
委托类层次图
.Net中所有具体委托类型其实都继承自MulticastDelegate,多播委托内部有一个数组,用来保存其它委托,这是所谓的“委托链”,有了它可以对多个委托进行组合,让一个委托可以一次执行多个操作。比如:
Action act = ()=>Console.WriteLine("动作1");
act += ()=>Console.WriteLine("动作2");
act += ()=>Console.WriteLine("动作3");
act();
//将打印出三行文字
这里编译器其实把+=操作符替换成了Delegate.Combine(Delegate a, Delegate b)方法,该方法内部又是直接调用第一个参数的CombineImpl方法。有兴趣的可以阅读其源码。
因此多播委托的作用在于,现实观察者模式不用手动维护一个订阅者集合了。于是上面的例子可以重构成这样:
Downloader.cs
class Downloader
{
public void Download(string resource)
{
for (int i = 1, size = 10; i this.OnProrgess(i, size);
Thread.Sleep(500);
}
}
//因为有多播委托,所以这里不需要“登记”、“注销”方法了
private void OnProrgess(int done, int total)
{
if (this.DownloadProgress != null)
this.DownloadProgress(done, total);
}
//NOTE: 这里为了用 +=,-=操作符替代“登记”、“注销”方法,不得以将订阅者集合暴露出去
public Actionint, int> DownloadProgress;
//进度回调链
}
删除IProgressMonitor接口,因为只需要委托签名匹配即可,不需要用接口来约束
Program.cs
class Program
{
static void Main(string[] args)
{
var resouce = "visual studio.iso";
var downloader = new Downloader();
downloader.DownloadProgress += new ProgressVeiw().OnProgress;
Console.WriteLine("正在下载 " + resouce);
downloader.Download(resouce);
Console.ReadKey();
}
}
注意上面Downloader.cs注释中的NOTE标记,在观察者模式中,发布者应该是它自己发布消息,而此处是public修饰,可以直接在外部调用,违背了观察者模式,因此我们需要使用事件来进行访问控制。
3.2 事件
方法相当简单,只需要添加一个event关键字
//用event修饰,这样外部只能进行-=、+=操作,调用只能在本类内部进行
public event Actionint, int> DownloadProgress;
//进度事件
那事件和委托有什么区别?其实事件和属性一样,属语法糖,编译器会为我们生成访问器方法和实际的实例变量。
比如上面,编译器生成了Action成员,把事件的+=,-=转换为一对public的addXxx、removeXxx方法。
如果不想让编译器为我们自动生成委托成员,我们也可以手动实现,如:
public event Actionint, int> DownloadProgress
{
add
{
_downloadProgress += value;
}
remove
{
_downloadProgress -= value;
}
}
private Actionint, int> _downloadProgress;
所以事件的作用在于,实现观察者模式只需要用event关键字定义一个实例变量即可
其它语言相关概念
C++
C/C++中的函数指针与.Net中委托的区别在于
函数指针很“赤裸”,是一个unsigned int类型的值,表示函数第一条CPU指令的内存地址
委托是比较复杂的CLR对象,内部封装了目标对象和方法指针,多播委托内部还维护了一个委托链
C++虽然没有内置观察者模式的实现,但许多第三方库如qt、boost都提供了“信号(signal)-槽(slot)”的功能。信号相当于.Net中的事件,槽相当于事件处理方法。
使用boost的signals2时需要注意的是,它实现的线程安全是指一个线程中添加、移除slot,不会影响另一个线程遍历slot集合。我们仍要注意对象被销毁的问题,比如一个slot在某线程中正在执行,而同时signal所属的那个对象在另一个线程被销毁了,这时访问那个对象会不会导致程序crash要看运气了。
Java
java没有匿名方法的语法,但可以用”匿名内部类 + 单方法接口”代替,java8对语法和api进行了扩展,引入了lambda,其实只是语法糖,本质还是接口。
有一些第三方类库提供了观察者模式的实现,比如:谷歌guava中的EventBus。
Ruby
ruby语法非常丰富,这里介绍下它的block(代码块,一种匿名方法的语法:do |param| ...end 和 { |param| do_something param },这个概念来自Smalltalk)。
我们在写程序时会经常需要实现“回调”的功能,即接收一个函数,然后在适当的时刻调用它,ruby直接原生支持了,任何方法都可以接收一个block作为隐式的参数,不用在参数列表中显式声明,在方法里可以用yield关键字调用传进来的block。如:
def process_data data
do_work(data) # 假装在处理数据
yield data if block_given? # 如果有block则调用它
end
process_data([1, 2, 3]) do |data|
puts '处理完成!'
end
再比如核心库中的File::open方法
# 调用该方法时如果
# 传入block则将打开的文件传给block,本方法结束后文件对象被销毁,这样不用麻烦调用者关闭文件了
# 否则
# 返回文件对象
File.open('file.txt') do |f|
puts f.readlines # 打印出所有行
end
再看ruby的一个web框架Sinatra的API
get '/home/index' do
'我是对get请求的响应'
end
post '/home/index' do
'我是对post请求的响应'
end
用这种语法来写HTTP Web应用是不是很爽?因为这种API设计可以算是实现了一种HTTP的DSL(领域专用语言)了。
如果你喜欢这种风格,可以试试.Net的开源web框架Nancy,我已用她写了两个项目,API很实用,比ASP.NET MVC灵活。
这里简单展示一下她的特色:content negotiation(内容协商,根据客户端请求返回指定类型内容)
Get["/"] = parameters => {
//这里返回的对象会被传递到nancy的content negotiation管道
//然后会检查请求头的accept类型,比如 如果是xml则该对象被序列化为xml,如果是json则被序列为json
return new MyModel();
};
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11