委托与事件
2016-01-19 16:03
405 查看
委托
C#委托对应于C++中的函数指针,也就是说我希望使用动态的特定的模块来执行代码,这也就是委托的作用。举一个例子,冒泡排序,我需要动态决定是大到小排序还是小到大排序,如果使用硬编码的办法那就是通过一个标志变量来进行判断决定。代码如下:
使用这种硬编码,用一个变量来判断决定,有一个非常大的弊端,就是当你的判断实现代码特别的大时候,容易造成代码臃肿而且结构不清晰。这时候可以使用委托,委托是一个引用类型,这句话有两层意思,首先是个类型,是个类型的话,那就要定义一个委托变量,第二个他是一个引用类型,那他很有可能是某种类,这地方先搁一下。
首先委托类型的申明,这个绕,是类型的申明,委托类型和其他类型不一样(比如int,double等),他需要类型的申明,这再次可以猜测他是某种类。
委托申明如下:
现在那就是要定义一个委托变量。可以这么定义:
其中的S2B是一个方法名,这个方法的签名要和之前申明的委托类型要一致,即返回类型、形参一致。具体代码如下:
这种委托变量的定义
比如说我定义方法
到这,一个简单的委托就这么愉快地实现了,如果你觉得委托就这么一点作用,那就大错特错了。委托最大的作用是多播委托功能,多播(multicast)的意思是多点发送,即将多个方法实现关联在一个委托变量上,通过这个委托变量的调用,来顺序调用这个变量所关联的方法。即发布-订阅模式,首先定义发布者和订阅者,然后发布者和订阅者连接,然后发布者通过发布通知给订阅者,订阅者自己该干嘛干嘛。举个例子,一个房间Room有个空调,空调设置在26度,空调中有两个温控模块一个是加热模块Heater,一个是降温模块Cooler,这两个模块都同时检测房间温度Temperature,如果低于26度,那Heater工作,开始加热,否则Cooler开始工作,开始降温。也就是Room是发布者,Heater和Cooler是订阅者,也就是说房间Room温度的变化TempChanged要动态调用Heater的TempChanged和Cooler的TempChanged。即发布者通过发布通知给订阅者,订阅者执行既定的任务。这个功能特别好,他能分离开发布和执行两个模块,从逻辑上编程思路很清晰。首先分别定义订阅者Heater和Cooler。
代码写到这,好像特别完美,但是这里面有两个重大弊端,第一个是订阅者和发布者的连接是通过+=操作符来实现的,这个不安全,因为首先发布者在发布通知的时候需要检查委托变量是否为null,因为如果为null,程序出错,所以上面代码
localChanged=XX方法名 它等价于TempHandler localChanged=new TemChangedHandler(XX方法名),这表明当一个委托类型调用=、+=、-=操作符(-=、+=最终还是=操作符嘛)的时候,委托类型其实是重新生成了一个新的实例对象。这样就能解释以上通过局部变量判断非空的原因了。但是无论怎么样,这个非空判断是可以可以优化的,Microsoft应该给我们做一个优化,所以我们一会要讲事件Event事件对这个情况就进行了优化,委托还有一个非常不好的弊端就是他没法对委托变量隐藏,即不能使用private类型修饰,因为如果用private修饰就不能在类的外部为委托添加方法了。这导致可以在类外部调用委托变量,这当然是不符合逻辑的,因为TempChanged是应该根据温度变化点用的,而这里温度很明显没有发生变化。
C#委托对应于C++中的函数指针,也就是说我希望使用动态的特定的模块来执行代码,这也就是委托的作用。举一个例子,冒泡排序,我需要动态决定是大到小排序还是小到大排序,如果使用硬编码的办法那就是通过一个标志变量来进行判断决定。代码如下:
<span style="font-size:14px;"> static void BublleSort(bool IsS2B, params int[] data) { for (int i = 0; i < data.Length; i++) { for (int j = 0; j < i; j++) { if (IsS2B) { if (data[j] > data[i]) { data[i] ^= data[j]; data[j] ^= data[i]; data[i] ^= data[j]; } } else { if (data[j] < data[i]) { data[i] ^= data[j]; data[j] ^= data[i]; data[i] ^= data[j]; } } } } Console.WriteLine("没有使用委托"); foreach (int item in data) { Console.Write("{0,3}", item); } Console.WriteLine(); }</span>调用:
BublleSort(true, 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);结果:
使用这种硬编码,用一个变量来判断决定,有一个非常大的弊端,就是当你的判断实现代码特别的大时候,容易造成代码臃肿而且结构不清晰。这时候可以使用委托,委托是一个引用类型,这句话有两层意思,首先是个类型,是个类型的话,那就要定义一个委托变量,第二个他是一个引用类型,那他很有可能是某种类,这地方先搁一下。
首先委托类型的申明,这个绕,是类型的申明,委托类型和其他类型不一样(比如int,double等),他需要类型的申明,这再次可以猜测他是某种类。
委托申明如下:
delegate bool SortOption(int a, int b);看着好别扭,首先是delegate关键字,表明是申明委托类型,其次从结构上看像一个函数,这可以这么理解委托类型是对应的C++的函数指针。然后这个”函数名“就是我们申明的委托类型。这个申明你可以放在类里面也可以放在类外,放在类里面那他的作用域就在这个类里,放在类外那就在这个名字空间内。
现在那就是要定义一个委托变量。可以这么定义:
<pre name="code" class="csharp"> SortOption delegateVariable; delegateVariable = S2B;
其中的S2B是一个方法名,这个方法的签名要和之前申明的委托类型要一致,即返回类型、形参一致。具体代码如下:
static bool S2B(int a, int b) { return a > b; }
这种委托变量的定义
SortOption delegateVariable;是不是看起来是不是和委托是某种类的说法矛盾的了。那他还可以这么定义:
SortOption delegateVariable1 = new SortOption(S2B);这个看起来是不是就像多了,这个要克服,这是因为Microsoft人家热心帮我简化了定义。
比如说我定义方法
static void BublleSort1(SortOption option, params int[] data) { for (int i = 0; i < data.Length; i++) { for (int j = 0; j < i; j++) { <span style="color:#ff0000;"> if (option(data[j], data[i]))</span> { data[i] ^= data[j]; data[j] ^= data[i]; data[i] ^= data[j]; } } } Console.WriteLine("使用委托"); foreach (int item in data) { Console.Write("{0,3}", item); } Console.WriteLine(); }如果按照上面的定义方法,调用这个方法就可以直接这么写:
BublleSort1(S2B, 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);但是按照下面的定义方法,要这么写:
BublleSort1(new SortOption(S2B), 1, 3, 4, 2, 7, 0, 6, 3, 1, 53, 23);从上面的方法的代码实现上看,使用委托确实代码清爽多了,一行解决复杂的逻辑判断问题。
到这,一个简单的委托就这么愉快地实现了,如果你觉得委托就这么一点作用,那就大错特错了。委托最大的作用是多播委托功能,多播(multicast)的意思是多点发送,即将多个方法实现关联在一个委托变量上,通过这个委托变量的调用,来顺序调用这个变量所关联的方法。即发布-订阅模式,首先定义发布者和订阅者,然后发布者和订阅者连接,然后发布者通过发布通知给订阅者,订阅者自己该干嘛干嘛。举个例子,一个房间Room有个空调,空调设置在26度,空调中有两个温控模块一个是加热模块Heater,一个是降温模块Cooler,这两个模块都同时检测房间温度Temperature,如果低于26度,那Heater工作,开始加热,否则Cooler开始工作,开始降温。也就是Room是发布者,Heater和Cooler是订阅者,也就是说房间Room温度的变化TempChanged要动态调用Heater的TempChanged和Cooler的TempChanged。即发布者通过发布通知给订阅者,订阅者执行既定的任务。这个功能特别好,他能分离开发布和执行两个模块,从逻辑上编程思路很清晰。首先分别定义订阅者Heater和Cooler。
class Heater { public Heater(double temp) { StandardTemp = temp; } public double StandardTemp { get; private set; } = 26; <span style="color:#ff0000;"> public void TempChanged(double temp) { if (temp < StandardTemp) Console.WriteLine("Heatder Start Working"); }</span> } class Cooler { public Cooler(double temp) { StandardTemp = temp; } public double StandardTemp { get; private set; } = 26; <span style="color:#ff0000;">public void TempChanged(double temp) { if (temp > StandardTemp) Console.WriteLine("Cooler Start Working"); }</span> }定义发布者Room类,通过TempChanged想订阅者发布通知。
class Room { public TempChangedHandler TempChanged; double StandardTemp = 26; private double _Temperature; public double Temperature { get { return _Temperature; } set { if (value != StandardTemp) <span style="color:#ff0000;"> TempChanged(value);</span> _Temperature = value; } } }Main方法里将发布者和订阅进行连接。
Room room = new Room(); Cooler cooler = new Cooler(26); Heater heater = new Heater(26); <span style="color:#ff0000;"> room.TempChanged += cooler.TempChanged; room.TempChanged += heater.TempChanged;</span> Console.WriteLine("当温度是25度时"); room.Temperature = 25; Console.WriteLine("当温度是24度时"); room.Temperature = 24; Console.ReadKey();运行结果:
代码写到这,好像特别完美,但是这里面有两个重大弊端,第一个是订阅者和发布者的连接是通过+=操作符来实现的,这个不安全,因为首先发布者在发布通知的时候需要检查委托变量是否为null,因为如果为null,程序出错,所以上面代码
<span style="color:#ff0000;"> TempChanged(value);</span>需要改成:
TempChangedHandler localChanged = TempChanged; if (localChanged != null) TempChanged(value);这个地方非常有意思,他并没有直接判断TempChanged是否为空,而是先赋值给一个局部变量localChanged,然后通过这个局部变量来判断是否为空。这有什么作用呢?多播委托经常会用在多线程程序中,如果在直接判断TempChanged是否为空之后,TempChanged()调用之前,其他线程调用了room.TempChanged-=XX操作,使room.TempChanged为null,这样程序就会出错,而且会莫名其妙。哈哈哈····,但是如果先赋值给一个局部变量,为什么就能避免这种情况呢?按理来说,委托是一个引用类型,赋值给一个局部变量,局部变量localChanged和TempChanged指向的是同一个位置,TempChanged的变化应该在localChanged表现出来,但是情况并不是这样,之前我们提到过委托变量定义可以使用 TempChangedHandler
localChanged=XX方法名 它等价于TempHandler localChanged=new TemChangedHandler(XX方法名),这表明当一个委托类型调用=、+=、-=操作符(-=、+=最终还是=操作符嘛)的时候,委托类型其实是重新生成了一个新的实例对象。这样就能解释以上通过局部变量判断非空的原因了。但是无论怎么样,这个非空判断是可以可以优化的,Microsoft应该给我们做一个优化,所以我们一会要讲事件Event事件对这个情况就进行了优化,委托还有一个非常不好的弊端就是他没法对委托变量隐藏,即不能使用private类型修饰,因为如果用private修饰就不能在类的外部为委托添加方法了。这导致可以在类外部调用委托变量,这当然是不符合逻辑的,因为TempChanged是应该根据温度变化点用的,而这里温度很明显没有发生变化。
room.TempChanged(0);
相关文章推荐
- 调用物流接口按时间正序显示数据
- 【OC开发工作笔记】之相机demo
- swift开发笔记20 图片上传的最简单办法
- IIS 7.0 部署MVC
- asp.net微信开发第一篇----开发者接入
- syscalls.h 与unitsd.h
- PHP的错误和异常处理
- 数据库迁移 - SQLServer->MySQL
- 需求 - 16 - 倒计时
- 【bzoj2705】[SDOI2012]Longge的问题 欧拉函数
- Unity 3D中的 SetActive() 与 OnEnable()、OnDisable()要注意的
- model1和model2
- 监控 DNS 流量,预防安全隐患五大招!
- Android图片加载神器之Fresco-加载图片基础[详细图解Fresco的使用]
- poj 1308 Is It A Tree?(并查集)
- Swift学习之闭包
- IT增值服务,客户案例(一)--山东青岛在职人士,2年.Net经验,转Java开发半年
- JS拖拽组件学习使用
- php 全局变量global的作用域
- nfs常见问题解决方法