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         /// <summary>
16         /// 计算总价
17         /// </summary>
18         /// <param name="count">数量</param>
19         /// <param name="price">单价</param>
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);

然后修改计算方法:

/// <summary>
        /// 计算总价
        /// </summary>
        /// <param name="count">数量</param>
        /// <param name="price">单价</param>
        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

    

转载于:https://www.cnblogs.com/monkeyZhong/p/4595444.html

C# 深入浅出 委托与事件相关推荐

  1. C#委托与事件学习笔记

    今天跟随视频学习了一下C#中最重要的一些概念之委托与事件.老杨的视频讲的还是挺深入浅出,不过刚接触C#.NET的人还是朦朦胧胧,就像张子阳先生说的"每次见到委托和事件就觉得心里别(biè)得 ...

  2. [C#]委托和事件(讲解的非常不错)

    引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...

  3. .NET基础示例系列之六:委托及事件

    委托是一个类. 定义委托时,实是定义一个用户自定义的类,它能代表具有相同参数列表和返回类型的任何方法,方法可以是静态方法或成员方法.示例: public partial class Form1 : F ...

  4. 大白话系列之C#委托与事件讲解(一)

    从序言中,大家应该对委托和事件的重要性有点了解了吧,虽然说我们现在还是能模糊,但是从我的大白话系列中,我会把这些概念说的通俗易懂的.首先,我们还是先说说委托吧,从字面上理解,只要是中国人应该都知道这个 ...

  5. 委托、事件、事件访问器

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 委托与事 ...

  6. js中的事件委托或是事件代理详解(转载)

    起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...

  7. JS事件委托或者事件代理原理以及实现

    事件委托(事件代理)原理:简单的说就是将事件交由别人来执行,就是将子元素的事件通过冒泡的形式交由父元素来执行. 为什么要用时间委托? 在JavaScript中,添加到页面上的事件处理程序数量将直接关系 ...

  8. 对C#下函数,委托,事件的一点理解!

    <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 今天一来是有点 ...

  9. 13委托和事件在观察者模式中的应用

    当一个被监视对象的方法执行会触发观察者Observer的方法的时候,我们就可以在被监视对象中声明委托和事件. 例子 有一个宠物追踪器挂宠物身上,只要宠物离开主人100米之外,主人手上的显示器显示警告信 ...

最新文章

  1. 一个Apache CollectionUtils.intersection 方法的简单问题
  2. 关于树论【LCA树上倍增算法】
  3. Kali Linux缺少ifconfig命令
  4. win10系统中查找端口
  5. C++旋转二维MxN矩阵的算法(附完整源码)
  6. 在线机器学习FTRL(Follow-the-regularized-Leader)算法介绍
  7. python交通标志识别_YOLOv3目标检测实战:交通标志识别
  8. 游戏ai 行为树_游戏AI –行为树简介
  9. android刷机方法,介绍一种android的裸刷机方法(fastboot刷机实质)
  10. hdu 4415 Assassin’s Creed 贪心
  11. 机器学习书籍资料推荐
  12. 移动端调试工具-Debuggap
  13. 计算机应用基础多媒体应用试题,2020年9月统考《计算机应用基础》多媒体技术试题及答案2...
  14. uniapp微信小程序使用分享功能
  15. Arduino DHT11温湿度传感器数据示例
  16. LeetCode 633.平方数之和(python题解)
  17. JS格式化字符串(两种方法)
  18. linux添加jetdirect协议,Linux系统中如何打印
  19. 数仓工具hive概述
  20. Oracle使用SQL*Plus生成html文件

热门文章

  1. Hi3516A开发--GV7601 硬件设计
  2. zcmu-1181(大数相加)
  3. js如何同时打开多个信息窗口 高德地图_高德地图显示单个窗体和显示多个窗体的方法...
  4. Java try和catch的使用
  5. Android系统启动-init篇
  6. go 获得 mysql 实际运行 SQL,Golang实践录:一个数据库迁移的代码记录
  7. long 比较大小_Long-Term Feature Banks
  8. github 地图上画区域的工程_筑工程测量区别
  9. 局域网流量控制_基于软件定义的网络准入控制体系
  10. oracle 强制恢复,oracle数据库恢复