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

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

我们先看一个例子:

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

  1. public enum CalcMethod
  2. {
  3. Normal,
  4. Debate
  5. }
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. CalcPrice(, ,CalcMethod.Normal);
  11. CalcPrice(, , 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 ;
  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. }

但是我们想一想,如果还要增加总价计算方式,那么我们是不是要不断的修改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. }

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

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

这种方法,我们破坏了对象的封装性,我们可以把委托类型的变量放到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. }

其中主函数的调用如下:

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

我们发现其调用有点怪怪的,  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 ;
  9. }
  10. }

其中主函数的调用如下:

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

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

  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 ;
  9. }
  10. }

其中主函数的调用如下:

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

我们给委托变量加上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 = ; i < ; i++)
  7. {
  8. temperature = i;
  9. if (temperature > )
  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. }

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

  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 = ; i < ; i++)
  9. {
  10. temperature = i;
  11. if (temperature > )
  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. }

调用的代码:

  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();

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

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

  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 = ; i < ; i++)
  18. {
  19. temperature = i;
  20. if (temperature > )
  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. }

调用代码:

  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();

微软的规范写法,如果要传递参数一般使用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

    

C# 深入浅出 委托与事件的更多相关文章

  1. (.NET高级课程笔记)委托、事件总结

      1.委托的声明.实例化和调用 同样的,也可以把事务写成上面的形式 2.泛型委托---Func.Action 3.委托的意义:解耦 4.委托的意义:异步多线程 5.委托的意义:多播委托 6.观察者模 ...

  2. C#委托和事件的使用的意义

    转载自:https://www.cnblogs.com/yinqixin/p/5056307.html 每一个初学C#的程序猿,在刚刚碰到委托和事件的概念时,估计都是望而却步,茫然摸不到头脑的.百度一 ...

  3. .NET面试题系列[7] - 委托与事件

    委托和事件 委托在C#中具有无比重要的地位. C#中的委托可以说俯拾即是,从LINQ中的lambda表达式到(包括但不限于)winform,wpf中的各种事件都有着委托的身影.C#中如果没有了事件,那 ...

  4. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  5. [转载]C#深入分析委托与事件

    原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...

  6. [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)

    原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委 ...

  7. C#委托与事件

    一.在控制台下使用委托和事件 我们都知道,C#中有"接口"这个概念,所谓的"接口"就是定义一套标准,然后由实现类来具体实现其中的方法,所以说"接口,是 ...

  8. C#委托与事件的简单使用

    前言:上一篇博文从原理和定义的角度介绍了C#的委托和事件.本文通过一个简单的小故事,来说明C#委托与事件的使用方法及其方便之处. 在阅读本文之前,需要你对委托和事件的基本概念有所了解.如果你是初次接触 ...

  9. C#之委托与事件

    委托与事件 废话一堆:网上关于委托.事件的文章有很多,一千个哈姆雷特就有一千个莎士比亚,以下内容均是本人个人见解. 1. 委托 1.1 委托的使用 这一小章来学习一下怎么简单的使用委托,了解一些基本的 ...

随机推荐

  1. Maven实战七

    转载:http://www.iteye.com/topic/973166 前言 Maven,发音是[`meivin],"专家"的意思.它是一个很好的项目管理工具,很早就进入了我的必 ...

  2. left join 、right join 、inner join和 full join的区别

    内连接      INNER JOIN(等值连接):只显示两个表中联结字段相等的行.这个和用select查询多表是一样的效果,所以很少用到: 外连接:LEFT JOIN :以左表为基础,显示左表中的所 ...

  3. 【HDOJ】3325 Arithmetically Challenged

    简单DFS. /* 3325 */ #include <iostream> #include <set> #include <cstdio> #include &l ...

  4. CH Round #58 - OrzCC杯noip模拟赛day2

    A:颜色问题 题目:http://ch.ezoj.tk/contest/CH%20Round%20%2358%20-%20OrzCC杯noip模拟赛day2/颜色问题 题解:算一下每个仆人到它的目的地 ...

  5. firefox HackBar组件模拟请求POST请求

    组件下载地址:https://addons.mozilla.org/zh-CN/firefox/addon/hackbar/

  6. C# 基础小知识之yield 关键字 语法糖

    原文地址:http://www.cnblogs.com/santian/p/4389675.html 对于yield关键字我们首先看一下msdn的解释: 如果你在语句中使用 yield 关键字,则意味 ...

  7. bzoj 1449 [JSOI2009]球队收益(费用拆分,最小费用流)

    1449: [JSOI2009]球队收益 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 547  Solved: 302[Submit][Status][ ...

  8. hdoj3351-stack

    Problem Description I'm out of stories. For years I've been writing stories, some rather silly, just ...

  9. CodeForces 55D Beautiful numbers(数位dp)

    数位dp,三个状态,dp[i][j][k],i状态表示位数,j状态表示各个位上数的最小公倍数,k状态表示余数 其中j共有48种状态,最大的是2520,所以状态k最多有2520个状态. #include ...

  10. dede 留言簿 多个

    使用后台的[模块]-[模块生成向导],然后填写一下资料 PS:complaints 是之前做的一个"举报投诉"的留言簿意思,这里用作非常多文件名称和新建数据表的名字,所以替换就可以 ...