委托是一个类,它定义了方法的类型,指明了这个委托类型的变量可接受的函数,表示对具有特定参数列表和返回类型的方法的引用,使得可以将方法当作另一个方法的参数来进行传递

不管什么函数只要返回值类型和参数能匹配委托所指定的,那么这个函数就能存储为一个委托变量的引用。

为什么需要委托

  • 委托可以将方法作为参数
  • 逻辑解耦,保持稳定
  • 代码复用,保证项目规范

委托使用步骤

  • 使用 delegate 关键字定义委托
  • 声明委托对应的方法
  • 实例化委托将方法作为参数传入

    1. class DelegateTest
    2. {
    3. //step01:使用delegate关键字定义委托
    4. public delegate int Sum(int x int y);
    5. static void Main(string[] args)
    6. {
    7. // step03:实例化委托将方法作为参数传入
    8. Sum sum = new Sum(new DelegateTest().Add);
    9. int result = sum.Invoke(1 2);
    10. Console.WriteLine(result);
    11. Console.ReadKey();
    12. }
    13. // step02:声明委托对应的方法
    14. public int Add(int x int y)
    15. {
    16. return x + y;
    17. }
    18. }

    step01:使用 delegate 关键字定义委托
    step02:声明委托对应的方法
    step03:实例化委托将方法作为参数传入

至此,一个委托就完成了。

匿名方法

上面说到完成一个委托要分三步走缺一步都不行,但是微软可能感觉这么实现比较麻烦,非要把三步做成两步来走!所以就用匿名方法来简化上边的三个步骤。

  1. // step01:首先用delegate定义一个委托
  2. public delegate int Sum(int x int y);
  3. static void Main(string[] args)
  4. {
  5. // step02:使用匿名方法的写法把一个方法赋值给委托
  6. Sum sum = delegate (int x int y) { return x + y; };
  7. int result = sum.Invoke(1 2);
  8. Console.WriteLine(result);
  9. Console.ReadKey();
  10. }

step01:使用 delegate 关键字定义委托
step02:使用匿名方法的写法把一个方法赋值给委托

这时会发现这里省略了定义方法这一步,将三步简化成了两步。

Lambda表达式

微软对C#的设计理念是简单易用。这时候发现对匿名方法的方式依旧不太满意,就想方设法的来简化 delegate(int x, int y) { return x + y; } 这个匿名方法,Lambda 就出现了。

lambda 运算符 => 左边列出了需要的参数,右边定义了赋予 lambda 变量的方法实现代码。

  1. // step01:首先用delegate定义一个委托
  2. public delegate int Sum(int x int y);
  3. static void Main(string[] args)
  4. {
  5. // 方法一:
  6. Sum sum1 = (int x int y) => { return x + y; };
  7. int result1 = sum1(1 2);
  8. // 方法二:
  9. Sum sum2 = (x y) => { return x + y; };
  10. int result2 = sum2(1 2);
  11. // 方法三:
  12. Sum sum3 = (x y) => x + y;
  13. int result3 = sum3(1 2);
  14. }

方法一:简单的把 delegate 去掉,在 (){} 之间加上 =>
方法二:在方法一的基础上把参数类型都干掉了
方法三:要干就干彻底些,把 {} 以及 return 关键字都去掉了

注意:这三种方法随便怎么写都行

Lambda表达式简写

如果 lambda 表达式只有一句,方法块内就可以省略花括号和 return 语句,这时编译器会添加一条隐式的 return 语句。

  1. Func<double double> func = param => param * param;

等价于

  1. Func<double double> func = param =>
  2. {
  3. return param * 2;
  4. };

泛型委托

随着.Net版本的不断升级,微软又来玩新花样了,不管是匿名方法还是 Lambda 表达式,完成一个委托的应用,都逃不过两个步骤,一步是定义一个委托,另一步是用一个方法来实例化一个委托。微软干脆把这两步都合成一步来走了。用 Func 来简化一个委托的定义。

  1. static void Main(string[] args)
  2. {
  3. //方法一:
  4. Func<int, int, int> add1 = (int x, int y) => { return x + y; };
  5. int result1 = add1(1 2);
  6. //方法二:
  7. Func<int, int, int> add2 = (x y) => { return x + y; };
  8. int result2 = add2(1 2);
  9. //方法三:
  10. Func<int int int> add3 = (x y) => x + y;
  11. int result3 = add3(1 2);
  12. }

至此一个委托的应用就可用 Func<int, int, int> add3 = (x, y) => x + y; 一句话来完成了,其中的 Func 就是所谓的泛型委托。

微软提供了 Action<T>Func<T> 两种泛型委托,用于简化方法定义。

Action

表示引用一个 void 返回类型的方法,可以传递最多16种不同的参数类型,没有泛型参数的 Action 类可调用没有参数的方法。

  1. // Action:无参数
  2. Action action1 = () => { Console.WriteLine("啦啦啦啦"); };
  3. action1();
  4. // Action:一个参数
  5. Action<string> action2 = p => { Console.WriteLine("啦啦啦啦,name:{0}",p); };
  6. action2("wang");
  7. // Action:多个参数
  8. Action<string, int> action3 = (name,age) => { Console.WriteLine("啦,name:{0},age:{1}", name,age); };
  9. action3("wang",25);

Func

Func<T> 允许调用带返回类型的方法,可以传递16种不同类型的参数和一个返回类型Func<out TResult> 委托类型可以调用带返回值且无参数的方法。

总结

  • Action<T> 用于没有返回值的方法(参数根据自己情况进行传递)
  • Func<T> 用于有返回值的方法(参数根据自己情况传递)

记住无返回就用 Action<T>,有返回就用 Func<T>

表达式树

表达式树其实与委托已经没什么关系了,如果非要扯上关系,表达式树是存放委托的容器。如果非要说的更专业一些,表达式树是存取 Lambda 表达式的一种数据结构。要用 Lambda 表达式的时候,直接从表达式中获取出来 Compile() 就可以直接用了。

  1. static void Main(string[] args)
  2. {
  3. Expression<Func<int, int, int>> exp = (x, y) => x + y;
  4. Func<int, int, int> fun = exp.Compile();
  5. int result = fun(1, 2);
  6. }

Invoke

  1. Sum sum = delegate (int x, int y) { return x + y; };
  2. int result = sum.Invoke(1, 2);
  3. //等价于
  4. int result = sum(1,2);

委托数组

定义 Math 类提供两个静态方法接收一个 double 类型的参数,用于计算倍数和阶乘。

  1. class Math
  2. {
  3. public static double MultipleTwo(double value)
  4. {
  5. return value * 2;
  6. }
  7. public static double Square(double value)
  8. {
  9. return value * value;
  10. }
  11. }

添加MathOperation操作方法,传递委托和double类型参数

  1. class Program
  2. {
  3. public delegate double MyDelegate(double value);
  4. static void Main(string[] args)
  5. {
  6. //定义委托数组
  7. MyDelegate[] myDelegates = {
  8. Math.MultipleTwo
  9. Math.Square
  10. };
  11. //使用委托数组
  12. for (int i = 0; i < myDelegates.Length; i++)
  13. {
  14. MathOperation(myDelegates[i], 3.7);
  15. MathOperation(myDelegates[i], 3.0);
  16. }
  17. Console.ReadKey();
  18. }
  19. public static void MathOperation(MyDelegate myDelegate double value)
  20. {
  21. var result = myDelegate(value);
  22. Console.WriteLine("Delegate is {0},value is {1}" myDelegateresult);
  23. }
  24. }

多播委托

之前的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次显式调用这个委托。

但委托中也可以包含多个方法,称为多播委托。多播委托可以按顺序调用多个方法,为此委托的签名必须返回void,否则就只能得到委托最后调用的最后一个方法的结果。

  1. Func<double, double> func = Math.MultipleTwo;
  2. func += Math.Square;
  3. var result = func(3.0);
  4. Console.WriteLine(result);
  5. MyDelegate myDelegate = Math.MultipleTwo;
  6. myDelegate += Math.Square;
  7. var result2 = myDelegate(3.0);
  8. Console.WriteLine(result2);
  9. Console.ReadKey();

只返回了3.0阶乘的值

+= 和 -=

多播委托使用 +=-=,在委托中增加或删除方法调用。

  1. static void Main(string[] args)
  2. {
  3. Action action = Print.First;
  4. action += Print.Second;
  5. action();
  6. Action action2 = Print.First;
  7. action2 += Print.Second;
  8. action2 -= Print.First;
  9. action2();
  10. Console.ReadKey();
  11. }
  12. class Print
  13. {
  14. public static void First()
  15. {
  16. Console.WriteLine("FirstMethod");
  17. }
  18. public static void Second()
  19. {
  20. Console.WriteLine("SecondMethod");
  21. }
  22. }

如果要使用多播委托,就要知道对同一个委托调用方法链的顺序并未正式定义,因此要避免编写依赖于特定顺序调用方法的代码。

多播委托异常处理

使用多播委托,意味着多播委托里包含一个逐个调用的委托集合,如果集合其中一个方法抛出异常.整个迭代就会停止。

  1. class Program
  2. {
  3. public delegate double MyDelegate(out double value);
  4. static void Main(string[] args)
  5. {
  6. Action action = Print.First;
  7. action += Print.Second;
  8. action();
  9. Console.ReadKey();
  10. }
  11. }
  12. class Print
  13. {
  14. public static void First()
  15. {
  16. Console.WriteLine("FirstMethod");
  17. throw new Exception("Error");
  18. }
  19. public static void Second()
  20. {
  21. Console.WriteLine("SecondMethod");
  22. }
  23. }

委托只调用了第一个方法,因为第一个方法抛出了异常,委托的迭代停止,不再调用 Second()

GetInvocationList

使用 Delegate的GetInvocationList() 方法自己迭代方法列表。

  1. Action action = Print.First;
  2. action += Print.Second;
  3. Delegate[] delegates = action.GetInvocationList();
  4. foreach (Action item in delegates)
  5. {
  6. try
  7. {
  8. item();
  9. }
  10. catch (Exception error)
  11. {
  12. Console.WriteLine(error.Message);
  13. }
  14. }
  15. Console.ReadKey();

修改后,程序在捕获异常后,会迭代下一个方法。

闭包的陷阱

https://www.cnblogs.com/aehyok/p/3730417.html

源码:

  1. List<Action> list = new List<Action>();
  2. for (int i = 0; i < 5; i++)
  3. {
  4. Action t = () => Console.WriteLine(i.ToString());
  5. list.Add(t);
  6. }
  7. foreach (Action t in list)
  8. {
  9. t();
  10. }

IL反编译

  1. List<Action> list = new List<Action>();
  2. TempClass tempClass = new TempClass();
  3. for (tempClass.i = 0; tempClass.i < 5; tempClass.i++)
  4. {
  5. Action t = tempClass.TempFunc;
  6. list.Add(t);
  7. }
  8. foreach (Action t in list)
  9. {
  10. t();
  11. }
  1. public class TempClass
  2. {
  3. public int i;
  4. public void TempFunc()
  5. {
  6. Console.WriteLine(i.ToString());
  7. }
  8. }

所谓的闭包对象,指的是上面这种情形中的 TempClass 对象。如果匿名方法(Lambda表达式)引用了某个局部变量,编译器就会自动将该引用提升到该闭包对象中。即将for循环中的变量 i 修改成了引用闭包对象的公共变量 i。这样一来,即使代码执行后离开了原局部变量 i 的作用域(如for循环),包含该闭包对象的作用域也还存在。
image.png
修改一下源代码

  1. List<Action> list = new List<Action>();
  2. for (int i = 0; i < 5; i++)
  3. {
  4. int temp = i;
  5. Action t = () => Console.WriteLine(temp.ToString());
  6. list.Add(t);
  7. }
  8. foreach (Action t in list)
  9. {
  10. t();
  11. }

image.png

事件

事件是一种引用类型,实际上也是一种特殊的委托。事件基于委托,是提供了发布/订阅机制的委托,事件是将委托封装,并对外公布了订阅和取消订阅的接口。

Example_01

不使用委托和事件的代码,耦合度高

  1. public class Player
  2. {
  3. public void Die()
  4. {
  5. GameConfiguration configuration = new GameConfiguration();
  6. configuration.OnPlayerDeath();
  7. UI ui = new UI();
  8. ui.OnPlayerDeath();
  9. }
  10. }
  11. public class UI
  12. {
  13. public void OnPlayerDeath()
  14. {
  15. Console.Write("GameOver");
  16. }
  17. }
  18. public class GameConfiguration
  19. {
  20. public int DeathNumber { get; set; }
  21. public void OnPlayerDeath()
  22. {
  23. DeathNumber++;
  24. Console.WriteLine(DeathNumber);
  25. }
  26. }
  1. [TestMethod]
  2. public void Example_01()
  3. {
  4. Player player = new Player();
  5. player.Die();
  6. Assert.IsTrue(true);
  7. }

Example_02

使用委托和事件解耦代码

  1. /// <summary>
  2. /// 热水器
  3. /// </summary>
  4. public class Heater
  5. {
  6. /// <summary>
  7. /// 温度字段
  8. /// </summary>
  9. private int temperature;
  10. /// <summary>
  11. /// 事件委托
  12. /// </summary>
  13. /// <param name="param"></param>
  14. public delegate void BoilHandler(int param);
  15. /// <summary>
  16. /// 将委托封装,并对外公布订阅和取消订阅的接口
  17. /// </summary>
  18. public event BoilHandler BoilEvent;
  19. public void BoilWater()
  20. {
  21. for (int i = 0; i <= 100; i++)
  22. {
  23. temperature = i;
  24. if (temperature > 95)
  25. {
  26. // 调用所有注册对象的方法
  27. BoilEvent?.Invoke(temperature);
  28. }
  29. }
  30. }
  31. }
  32. /// <summary>
  33. /// 警报器
  34. /// </summary>
  35. public class Alarm
  36. {
  37. public void Alert(int param)
  38. {
  39. Console.WriteLine("Alarm:dddddddd,水已经 {0} 度了:" param);
  40. }
  41. }
  42. /// <summary>
  43. /// 显示器
  44. /// </summary>
  45. public static class Display
  46. {
  47. public static void ShowMsg(int param)
  48. {
  49. Console.WriteLine("Display:水已烧开,当前温度:{0}度." param);
  50. }
  51. }
  1. [TestMethod]
  2. public void Example_02()
  3. {
  4. Heater heater = new Heater();
  5. Alarm alarm = new Alarm();
  6. // 注册方法
  7. heater.BoilEvent += alarm.Alert;
  8. //heater.BoilEvent += (new Alarm()).MakeAlert; // 匿名对象注册方法
  9. // 注册静态方法
  10. heater.BoilEvent += Display.ShowMsg;
  11. // 烧水,会自动调用注册过对象的方法
  12. heater.BoilWater();
  13. Assert.IsTrue(true);
  14. }

Example_03

  • 委托类型的名称都应该以EventHandler 结束
  • 委托的原型定义:有一个 void 返回值,并接受两个输入参数:一个 Object 类型,一个 EventArgs 类型(或继承自EventArgs)
  • 事件的命名为委托去掉 EventHandler 之后剩余的部分.
  • 继承自 EventArgs 的类型应该以 EventArgs 结尾

委托声明原型中的 Object 类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器).回调函数(比如Alarm的Alert)可以通过它访问触发事件的对象(Heater),EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature

  1. public class Heater
  2. {
  3. /// <summary>
  4. /// 温度
  5. /// </summary>
  6. private int temperature;
  7. /// <summary>
  8. /// 添加型号作为演示
  9. /// </summary>
  10. public string type = "01";
  11. /// <summary>
  12. /// 添加产地作为演示
  13. /// </summary>
  14. public string area = "China";
  15. public delegate void BoiledEventHandler(object sender BoiledEventArgs e);
  16. public event BoiledEventHandler Boiled;
  17. /// <summary>
  18. /// 定义 BoiledEventArgs类,传递给 Observer 所感兴趣的信息
  19. /// </summary>
  20. public class BoiledEventArgs : EventArgs
  21. {
  22. public readonly int temperature;
  23. public BoiledEventArgs(int temperature)
  24. {
  25. this.temperature = temperature;
  26. }
  27. }
  28. /// <summary>
  29. /// 提供继承自Heater的类重写,以便继承类拒绝其他对象对它的监视
  30. /// </summary>
  31. /// <param name="e"></param>
  32. protected virtual void OnBoiled(BoiledEventArgs e)
  33. {
  34. // 如果有对象注册
  35. Boiled?.Invoke(this e); // 调用所有注册对象的方法
  36. }
  37. public void BoilWater()
  38. {
  39. for (int i = 0; i <= 100; i++)
  40. {
  41. temperature = i;
  42. if (temperature > 95)
  43. {
  44. // 建立BoiledEventArgs 对象.
  45. BoiledEventArgs e = new BoiledEventArgs(temperature);
  46. OnBoiled(e); // 调用 OnBolied 方法
  47. }
  48. }
  49. }
  50. }
  51. // 警报器
  52. public class Alarm
  53. {
  54. public void MakeAlert(object sender Heater.BoiledEventArgs e)
  55. {
  56. Heater heater = (Heater)sender;
  57. Console.WriteLine("Alarm:{0} - {1}: " heater.area heater.type);
  58. Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:" e.temperature);
  59. Console.WriteLine();
  60. }
  61. }
  62. // 显示器
  63. public static class Display
  64. {
  65. public static void ShowMsg(object sender Heater.BoiledEventArgs e)
  66. {
  67. //静态方法
  68. Heater heater = (Heater)sender;
  69. Console.WriteLine("Display:{0} - {1}: " heater.area heater.type);
  70. Console.WriteLine("Display:水快烧开了,当前温度:{0}度." e.temperature);
  71. Console.WriteLine();
  72. }
  73. }
  1. [TestMethod]
  2. public void Example_03()
  3. {
  4. Heater heater = new Heater();
  5. Alarm alarm = new Alarm();
  6. heater.Boiled += alarm.MakeAlert; //注册方法
  7. //heater.Boiled += (new Alarm()).MakeAlert; //给匿名对象注册方法
  8. //heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
  9. heater.Boiled += Display.ShowMsg; //注册静态方法
  10. heater.BoilWater(); //烧水,会自动调用注册过对象的方法
  11. Assert.IsTrue(true);
  12. }

参考

https://www.cnblogs.com/jujusharp/archive/2011/08/04/2127999.html
https://www.cnblogs.com/HQFZ/p/4903400.html
https://www.cnblogs.com/wangjiming/p/8300103.html