热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C#深入浅出委托与事件

C#中的委托和事件的概念接触很久了,但是一直以来总没有特别透彻的感觉,现在我在这里总结一下:首先我们要知道委托的由来,为什么要使用委托了?我们先看一个例子:假设我们有这样一个需求,需要计算在不同

C#中的委托和事件的概念接触很久了,但是一直以来总没有特别透彻的感觉,现在我在这里总结一下:

首先我们要知道委托的由来,为什么要使用委托了?

我们先看一个例子:

假设我们有这样一个需求,需要计算在不同方式下的总价,如下面代码所示,这里假设只有两种方式,一种是正常价格,一种是折扣价格:

 1  public enum CalcMethod
2 {
3 Normal,
4 Debate
5 }
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 CalcPrice(10, 100,CalcMethod.Normal);
11 CalcPrice(10, 100, CalcMethod.Debate);
12 Console.ReadLine();
13 }
14
15 ///
16 /// 计算总价
17 ///

18 /// 数量
19 /// 单价
20 public static double CalcPrice(int count,int price,CalcMethod method)
21 {
22 switch (method)
23 {
24 case CalcMethod.Normal:
25 return NormalPrice(count, price);
26 case CalcMethod.Debate:
27 return DebatePrice(count, price);
28 default:
29 return 0;
30 }
31 }
32
33 public static double NormalPrice(int count,int price)
34 {
35 Console.WriteLine("正常的价格是:{0}", count * price);
36 return count * price;
37 }
38 public static double DebatePrice(int count, int price)
39 {
40 Console.WriteLine("折扣的价格是:{0}", count * price*0.7);
41 return count * price*0.7;
42 }
43 }
View Code

但是我们想一想,如果还要增加总价计算方式,那么我们是不是要不断的修改CalcPrice方法,CalcMethod枚举呢?
那么是不是有更好的方式呢?

我们可以把真正的计算价格的方式委托给一个函数来计算,这样委托就诞生了。

首先我们定义一个跟方法参数和返回值类型一样的委托类型:

 public delegate double CalcPriceDelegate(int count, int price);

然后修改计算方法:

        ///


        /// 计算总价
        ///

        /// 数量
        /// 单价
        public static double CalcPrice(int count, int price, CalcPriceDelegate calcDelegate)
        {
            return calcDelegate(count, price);
        }

然后调用的时候直接用签名相同的方法传递就可以了:

           CalcPrice(10, 100, NormalPrice);
            CalcPrice(10, 100, DebatePrice);

     到这里我们大体明白了委托可以使用方法作为参数,这样就避免了程序中出现大量的条件分支语句,程序的扩展性好。

   接下来我要对委托做一个深入的探讨:

  委托首先其实也是一个类,

          public delegate double CalcPriceDelegate(int count, int price);

  上面这句话其实就是申明一种委托类型,这个类型在编译的时候会生成以下成员:

    1)public extern CalcPriceDelegate(object @object, IntPtr method);

      第一个参数是记录委托对象包装的实例方法所在的对象(this,如果包装的是静态方法,就为NULL),第二个参数就是表示要回调的方法。

    2) public virtual extern double Invoke(int count, int price);//同步调用委托方法

    3)public virtual extern IAsynResult BeginInvoke(int count, int price, AsyncCallback callback, object @object);  这个是异步执行委托方法,前面两个参数是委托的方法的输入参数,callback是回调方法,也就是说方法执行完成后的回调方法,AsyncCallback本身也是一个委托类型,其原型是:

     public delegate void AsyncCallback(IAsyncResult ar);

      最后一个参数是回调所需要的输入参数,这个参数会隐含在IAsyncResult的AsyncState中。

    另外返回值也是一个IAsyncResult结果。

            与之相对应的,public virtual extern double EndInvoke(IAsyncResult result)

    结束异步回调。

    以前对IAsyncResult,还有AsyncCallback都有点陌生,其实我们可以这样来理解,我想要一个方法异步来执行,那么肯定就需要调用BeginInvoke,那么我如何又能知道什么时候这个异步的调用结束呢?这就需要用到AsyncCallback这个异步回调,如果这个回调需要参数,就赋值给object,如果不确定是否异步执行完,就要用EndInvoke来确保结束,输入参数就是BeginInvoke的返回值IAsynResult,就相当于BeginInvoke的时候开出了一个收据,EndInvoke又把这个收据还了。

          这个话题要想深入下去就太多了,我们还是回到委托上来,委托时一个类,其编译后就是这么些个成员。

   我平时调用的时候经常会被各种各样的调用方法给搞糊涂了,这里总结下各种调用方式:

     假设委托类型为:public delegate double CalcPriceDelegate(int count, int price); 这就相当于定义了一个类,

    接下来就是赋值了(相当于申明对象):

    1)CalcPriceDelegate calcDelegate = new CalcPriceDelegate(NormalPrice). 这是最完整的赋值方式。

    2) CalcPriceDelegate calcDelegate = NormalPrice; 直接赋值方法,编译器会自动帮我们构造成第一种赋值方式。

    赋值完成后接下来就是如何调用了:

    1)calcDelegate(10,100);

    2)calcDelegate.Invoke(10,100)  与1)方法是一样的。

    3)calcDelegate.BeginInvoke(10,100,null,null) 异步执行

    委托还可以通过+=来添加方法,委托给多个方法,但是第一个必须是=,否则没有初始化,相对的,可以使用-=来移除方法。

    至于匿名委托,lambda表达式是一样的,把握本质就可以了,还需要了解MS定义的委托类型,这里暂时不讲了。

 

    接下来讲事件:

    第一步,我们还是引用上面的例子,只是把相关的代码放到一个类里面:

    

1     public delegate double CalcPriceDelegate(int count, int price);
2 public class CalcPriceClass
3 {
4 public double Calc(int count, int price, CalcPriceDelegate calcDelegate)
5 {
6 return calcDelegate(count, price);
7 }
8 }
View Code

        其中主函数里面的调用如下:

1             Console.WriteLine("演示引入事件的第一步:");
2 CalcPriceClass cp = new CalcPriceClass();
3 cp.Calc(10, 100, NormalPrice);
4 cp.Calc(10, 100, DebatePrice);
5 Console.ReadLine();
View Code

          这种方法,我们破坏了对象的封装性,我们可以把委托类型的变量放到CalcPriceClass类里面。
于是我们就有了第二步:

   

1  public class CalcPriceClass2
2 {
3 public CalcPriceDelegate m_delegate;
4 public double Calc(int count, int price, CalcPriceDelegate calcDelegate)
5 {
6 return calcDelegate(count, price);
7 }
8 }
View Code

 其中主函数的调用如下:

1             Console.WriteLine("演示引入事件的第二步:");
2 CalcPriceClass2 cp2 = new CalcPriceClass2();
3 cp2.m_delegate = NormalPrice;
4 cp2.m_delegate += DebatePrice;
5 cp2.Calc(10, 100, cp2.m_delegate);
6 Console.ReadLine();
View Code

我们发现其调用有点怪怪的,  cp2.Calc(10, 100, cp2.m_delegate);既然我为cp2的委托对象赋值了,这个时候其实没有必要再去传递这个委托对象了,于是就有了第三步:

 1  public class CalcPriceClass3
2 {
3 public CalcPriceDelegate m_delegate;
4 public double Calc(int count, int price)
5 {
6 if (m_delegate != null)
7 return m_delegate(count, price);
8 else return 0;
9 }
10 }
View Code

其中主函数的调用如下:

1            Console.WriteLine("演示引入事件的第三步:");
2 CalcPriceClass3 cp3 = new CalcPriceClass3();
3 cp3.m_delegate = NormalPrice;
4 cp3.m_delegate += DebatePrice;
5 cp3.Calc(10, 100);
6 Console.ReadLine();
View Code

在这步完成后,貌似达到了我们想要的结果,但是还是有些隐患的,因为我们可以随意的给委托变量赋值,所以就有了第四步,加上了事件:

 1  public class CalcPriceClass4
2 {
3 public event CalcPriceDelegate m_delegate;
4 public double Calc(int count, int price)
5 {
6 if (m_delegate != null)
7 return m_delegate(count, price);
8 else return 0;
9 }
10 }
View Code

其中主函数的调用如下:

1             Console.WriteLine("演示引入事件的第四步:");
2 CalcPriceClass4 cp4 = new CalcPriceClass4();
3 cp4.m_delegate += NormalPrice;
4 cp4.m_delegate += DebatePrice;
5 cp4.Calc(10, 100);
6 Console.ReadLine();
View Code

我们给委托变量加上event后有什么不一样呢?
这个时候我们不能直接给这个事件对象进行赋值,因为其内部是一个私有变量了,另外编译器会增加两个公共函数,

一个是add_m_delegate(对应+=), 

public void add_m_delegate(CalcPriceDelegate value)

{

  this.m_delegate = (CalcPriceDelegate)Delegate.Combine(this.m_delegate, value);

}

一个是remove_m_delegate(对应-=),

public void remove_m_delegate(CalcPriceDelegate value)

{

  this.m_delegate = (CalcPriceDelegate)Delegate.Remove(this.m_delegate, value);

}

另外内部的私有字段是这样子的:private CalcPriceDelegate m_delegate;

通过这四步,我们可以知道了从委托到事件的一个过程,其实事件也是委托,只是编译器会帮我们做一些事情而已。

 

事件 与 Observer设计模式

 

假设一个热水器,在温度达到95度以上的时候,警报器报警,并且显示器显示温度。

代码如下:

 1  public class Heater
2 {
3 private int temperature;
4 public void BoilWater()
5 {
6 for (int i = 0; i <100; i++)
7 {
8 temperature = i;
9 if (temperature > 95)
10 {
11 MakeAlert(temperature);
12 ShowMsg(temperature);
13 }
14 }
15 }
16 private void MakeAlert(int param)
17 {
18 Console.WriteLine("Alarm:滴滴滴,水已经{0}度了", param);
19
20 }
21 private void ShowMsg(int param)
22 {
23 Console.WriteLine("Display:水快开了,当前温度:{0}度。", param);
24 }
25 }
View Code

假设热水器由三部分组成:热水器、警报器、显示器,它们来自于不同厂商并进行了组装。那么,应该是热水器仅仅负责烧水,它不能发出警报也不能显示水温;在水烧开时由警报器发出警报、显示器显示提示和水温。如果是上面的代码可能就不合适了,就要使用下面的代码:

 1  public class Heater2
2 {
3 private int temperature;
4 public delegate void BoilHanlder(int param);
5 public event BoilHanlder BoilEvent;
6 public void BoilWater()
7 {
8 for (int i = 0; i <100; i++)
9 {
10 temperature = i;
11 if (temperature > 95)
12 {
13 if (BoilEvent != null)
14 BoilEvent(temperature);
15 }
16 }
17 }
18 }
19 public class Alarm
20 {
21 public static void MakeAlert(int param)
22 {
23 Console.WriteLine("Alarm:滴滴滴,水已经{0}度了", param);
24
25 }
26 }
27 public class Display
28 {
29 public static void ShowMsg(int param)
30 {
31 Console.WriteLine("Display:水快开了,当前温度:{0}度。", param);
32 }
33 }
View Code

调用的代码:

1            Console.WriteLine("演示热水器热水机报警及显示水温 观察者模式");
2 Heater2 ht2 = new Heater2();
3 ht2.BoilEvent += new Heater2.BoilHanlder(Alarm.MakeAlert);
4 ht2.BoilEvent += Display.ShowMsg;
5 ht2.BoilWater();
6 Console.ReadLine();
View Code

比较以上两种方式的不同,后面的这种更加符合面向对象的思想,因为作为热水器而言,主要的工作是热水,至于报警,显示温度是由其他器件来显示,是需要显示器,报警器这些观察者来观察热水器这个观察对象主体的温度。
到这里为止,我们知道了如何声明委托类型,申明委托对象,调用委托方法,委托类的实际成员,以及从委托到事件的演变,事件的表象与在编译后的实际成员,以及作为观察者模式使用事件的过程。

不过微软的规范写法却不是这样,下面改用微软的规范写法:

 1     public class Heater3
2 {
3 public string type = "RealFire 001";
4 public string area = "China Xian";
5 private int temperature;
6 public delegate void BoiledEventHandler(object sender,BoiledEventArgs e);
7 public event BoiledEventHandler Boiled;
8 protected virtual void OnBoiled(BoiledEventArgs e)
9 {
10 if (Boiled != null)
11 {
12 Boiled(this, e);
13 }
14 }
15 public void BoilWater()
16 {
17 for (int i = 0; i <100; i++)
18 {
19 temperature = i;
20 if (temperature > 95)
21 {
22 BoiledEventArgs e = new BoiledEventArgs(temperature);
23 OnBoiled(e);
24 }
25 }
26 }
27 }
28 public class BoiledEventArgs : EventArgs
29 {
30 public readonly int temperature;
31 public BoiledEventArgs(int temp)
32 {
33 temperature = temp;
34 }
35 }
36 public class Alarm3
37 {
38 public void MakeAlert(object sender, BoiledEventArgs e)
39 {
40 Heater3 ht = (Heater3)sender;
41 Console.WriteLine("Alarm:{0}-{1}", ht.area, ht.type);
42 Console.WriteLine("Alarm:滴滴滴,水已经{0}度了:", e.temperature);
43 Console.WriteLine();
44 }
45 }
46 public class Display3
47 {
48 public static void ShowMsg(object sender, BoiledEventArgs e)
49 {
50 Heater3 ht = (Heater3)sender;
51 Console.WriteLine("Display:{0}-{1}",ht.area,ht.type);
52 Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
53 Console.WriteLine();
54 }
55
56 }
View Code

调用代码:

1            Console.WriteLine("演示热水器热水机报警及显示水温 符合微软模式");
2 Heater3 ht3 = new Heater3();
3 Alarm3 al3 = new Alarm3();
4 ht3.Boiled += al3.MakeAlert;
5 ht3.Boiled += Display3.ShowMsg;
6 ht3.BoilWater();
7 Console.ReadLine();
View Code

微软的规范写法,如果要传递参数一般使用EventArgs或者其继承类,且继承类的命名以EventArgs结尾。
委托类型的名称以EventHandler结束。

委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。

一般这个Object类型指的是观察的对象,我们可以这样来记忆,因为委托就相当于方法,其实执行的就是观察者,那么观察者总要知道观察谁,以及观察所需要的参数。

其实事件还有一些概念:事件订阅者,事件接收者,事件发送者,这些以后再补充

 

这里引用了这位仁兄的博客:

http://www.tracefact.net/csharp-programming/delegates-and-events-in-csharp.aspx

 代码:

 http://files.cnblogs.com/files/monkeyZhong/CSharpDelegateAndEvent.zip

 

 

 

 

    


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
author-avatar
苦--但是依然love着你
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有