委托 Delegate的定义

  • 委托(delegate)是函数指针的“升级版”

    C语言函数指针

    • 示例:C/C++中的函数指针 ```c

      include

typedef int(*Calc)(int a, int b);//函数指针。 //声明有两个int形参,返回类型为int的函数指针类型 int Add(int a, int b) { int result = a + b; return result; }

int Sub(int a, int b) { int result = a - b; return result; }

int main() { int x = 100; int y = 200; int z = 0;

  1. Calc funPoint1 = &Add;
  2. Calc funPoint2 = ⋐
  3. z = funPoint1(x, y); //间接调用函数
  4. printf("%d+%d=%d\n", x, y, z);
  5. z = Sub(x, y); //直接调用函数
  6. printf("%d+%d=%d\n", x, y, z);
  7. system("pause"); //暂停程序运行,等待下一步操作执行结束
  8. return 0;

}

  1. - **一切皆地址**
  2. - 变量(数据)是以某个地址为起点的一段内存中所存储的值
  3. - 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
  4. - **直接调用与间接调用**
  5. - 直接调用:通过函数名来调用函数,CPU 通过函数名直接获得函数所在地址并开始执行->返回
  6. - 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行->返回
  7. <a name="Java"></a>
  8. ## Java
  9. Java 语言由 C++ 发展而来,为了提高应用安全性,Java 语言禁止程序员直接访问内存地址。即 Java 语言把 C++ 中所有与指针相关的内容都舍弃掉了。
  10. - **Java中没有与委托相对应的功能实体**
  11. <a name="3c299cc6"></a>
  12. ## 委托实例 Action 与 Func
  13. - **委托的简单使用**
  14. - Action委托
  15. - Func委托
  16. Action Func C# 内置的委托实例,它们都有很多重载以方便使用。
  17. ```c
  18. using System;
  19. using System.Collections.Generic;
  20. namespace DelegateExample
  21. {
  22. class Program
  23. {
  24. static void Main(string[] args)
  25. {
  26. Calculator calculator = new Calculator();
  27. Action action = new Action(calculator.Report);
  28. calculator.Report();
  29. action.Invoke();
  30. action();
  31. Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);//委托
  32. Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
  33. int x = 100;
  34. int y = 200;
  35. int z = 0;
  36. z = func1.Invoke(x, y); //间接调用
  37. Console.WriteLine(z);
  38. z = func2(x, y); //指针类写法,效果不变
  39. Console.WriteLine(z);
  40. }
  41. }
  42. class Calculator
  43. {
  44. public void Report()
  45. {
  46. Console.WriteLine("I have 3 Methods.");
  47. }
  48. public int Add(int a,int b)
  49. {
  50. int result = a + b;
  51. return result;
  52. }
  53. public int Sub(int a, int b)
  54. {
  55. int result = a - b;
  56. return result;
  57. }
  58. }
  59. }

委托的声明(自定义委托)

  • 委托是一种类(class) , 类是数据类型所以委托也是一种数据类型
  • 它的声名方式与一般的类不同,主要是为了照顾可读性和C/C++传统
  • 注意声明委托的位置
    • 避免写错地方结果声明成嵌套类型
  • 委托与所封装的方法必需”类型兼容”

19委托详解 - 图1
返回值的数据类型一致
参数列表在个数和数据类型上一致(参数名不需要一样)

委托是一种类:

  1. static void Main(string[] args)
  2. {
  3. Type t = typeof(Action);
  4. Console.WriteLine(t.IsClass);
  5. }

19委托详解 - 图2
委托是类,所以声明位置是和 class 处于同一个级别。但 C# 允许嵌套声明类(一个类里面可以声明另一个类),所以有时也会有 delegate 在 class 内部声明的情况。

实例:

  1. public delegate double Calc(double x, double y);
  2. class Program
  3. {
  4. static void Main(string[] args)
  5. {
  6. var calculator = new Calculator();
  7. var calc1 = new Calc(calculator.Mul);
  8. Console.WriteLine(calc1(5, 6));
  9. }
  10. }
  11. class Calculator
  12. {
  13. public double Mul(double x, double y)
  14. {
  15. return x * y;
  16. }
  17. public double Div(double x, double y)
  18. {
  19. return x / y;
  20. }
  21. }

委托的一般使用

  • 实例:把方法当作参数传给另一个方法
    • 正确使用1:模板方法,“借用”指定的外部方法来产生结果
      • 相当于”填空题”
      • 常位于代码中部
      • 委托有返回值
    • 正确使用2:回调(callback) 方法, 调用指定的外部方法
      • 相当于”流水线”
      • 常位于代码末尾
      • 委托无返回值
  • 注意:难精通+易使用+功能强大东西,一旦被滥用则后果非常严重

    • 缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
    • 缺点2:使可读性下降、debug的难度增加
    • 缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
    • 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降

      模板方法

      利用模板方法,提高代码复用性。
      下例中 Product、Box、WrapFactory 都不用修改,只需要在 ProductFactory 里面新增不同的 MakeXXX 然后作为委托传入 WrapProduct 就可以对其进行包装。

      1. class Program
      2. {
      3. static void Main(string[] args)
      4. {
      5. ProductFactory productFactory = new ProductFactory();
      6. WarpFactory warpFactory = new WarpFactory();
      7. Func<Product> fun1 =new Func<Product>(productFactory.MakePizza);//封装制作Pizza方法
      8. Func<Product> fun2 =new Func<Product>(productFactory.MakeToyCar);//封装制作Toy Car方法
      9. Func<Product> fun3 = new Func<Product>(productFactory.MakeUmbrella);
      10. //调用模板方法
      11. Box box1 = warpFactory.WarpProduct(fun1);
      12. Box box2 = warpFactory.WarpProduct(fun2);
      13. Box box3 = warpFactory.WarpProduct(fun3);
      14. //打印输出
      15. Console.WriteLine(box1.Product.Name);
      16. Console.WriteLine(box2.Product.Name);
      17. Console.WriteLine(box3.Product.Name);
      18. }
      19. }
      20. class Product
      21. {
      22. public string Name { get; set; }
      23. }
      24. class Box
      25. {
      26. public Product Product { get; set; }
      27. }
      28. //使用模板方法提高复用性
      29. class WarpFactory//模板方法逻辑
      30. {
      31. public Box WarpProduct(Func<Product> getProduct )
      32. {
      33. Box box = new Box();
      34. Product Product = getProduct.Invoke();
      35. box.Product = Product;
      36. return box;
      37. }
      38. }
      39. //书写成模板格式,Product、Box、WarpFactory都不需要改动,仅需要更改ProdcutFactory类
      40. class ProductFactory
      41. {
      42. public Product MakePizza()
      43. {
      44. Product product = new Product();
      45. product.Name = "Pizza";
      46. return product;
      47. }
      48. public Product MakeToyCar()
      49. {
      50. Product product = new Product();
      51. product.Name = "Toy Car";
      52. return product;
      53. }
      54. public Product MakeUmbrella()
      55. {
      56. Product product = new Product();
      57. product.Name = "Umbrella";
      58. return product;
      59. }
      60. }

      Reuse,重复使用,也叫“复用”。代码的复用不但可以提高工作效率,还可以减少 bug 的引入。
      良好的复用结构是所有优秀软件所追求的共同目标之一。

      回调方法(Hollywood方法)


      回调方法是通过委托类型参数传入主调方法的被调用方法,主调方法根据自己的逻辑决定是否调用这个方法。 ```csharp using System; using System.Collections.Generic;

namespace DelegateExample { class Program { static void Main(string[] args) { var productFactory = new ProductFactory();

  1. // Func 前面是传入参数,最后一个是返回值,所以此处以 Product 为返回值
  2. Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
  3. Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
  4. var wrapFactory = new WrapFactory();
  5. Logger logger = new Logger();
  6. // Action 只有传入参数,所以此处以 Product 为参数
  7. Action<Product> log = new Action<Product>(logger.Log);
  8. Box box1 = wrapFactory.WrapProduct(func1, log);
  9. Box box2 = wrapFactory.WrapProduct(func2, log);
  10. Console.WriteLine(box1.Product.Name);
  11. Console.WriteLine(box2.Product.Name);
  12. }
  13. }
  14. class Logger//记录程序运行状态
  15. {
  16. public void Log(Product product)
  17. {
  18. // Now 是带时区的时间,存储到数据库应该用不带时区的时间 UtcNow。
  19. Console.WriteLine("Product '{0}' created at {1}.Price is {2}", product.Name, DateTime.UtcNow, product.Price);
  20. }
  21. }
  22. class Product
  23. {
  24. public string Name { get; set; }
  25. public double Price { get; set; }
  26. }
  27. class Box
  28. {
  29. public Product Product { get; set; }
  30. }
  31. class WrapFactory
  32. {
  33. // 模板方法,提高复用性
  34. public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallBack)
  35. //没有返回值的委托使用action<>
  36. {
  37. var box = new Box();
  38. Product product = getProduct.Invoke();
  39. // 只 log 价格高于 50 的
  40. if (product.Price >= 50)
  41. {
  42. logCallBack(product);
  43. }
  44. box.Product = product;
  45. return box;
  46. }
  47. }
  48. class ProductFactory
  49. {
  50. public Product MakePizza()
  51. {
  52. var product = new Product
  53. {
  54. Name = "Pizza",
  55. Price = 12
  56. };
  57. return product;
  58. }
  59. public Product MakeToyCar()
  60. {
  61. var product = new Product
  62. {
  63. Name = "Toy Car",
  64. Price = 100
  65. };
  66. return product;
  67. }
  68. }

}

  1. <a name="Pb3nc"></a>
  2. ## 注意委托滥用
  3. 恐怖的委托滥用示例,基本上工作在这段代码上的人,三个月内就离职了。 <br />以后技术上去了,记得回看这段代码,警示自己。
  4. :::danger
  5. **错误示范代码**
  6. :::
  7. ```csharp
  8. using System;
  9. using System.Collections.Generic;
  10. namespace DelegateExample
  11. {
  12. class Program
  13. {
  14. static void Main(string[] args)
  15. {
  16. Operation opt1=new Operation();
  17. Operation opt2=new Operation();
  18. Operation opt3=new Operation();
  19. opt3.InnerOperation = opt2;
  20. opt2.InnerOperation = opt1;
  21. opt3.Operate(new object(), null, null);
  22. //问题1:如果传入的两个参数为null, 失败和成功的效果是什么?答:内层的操作会调用外层的回调!
  23. //问题2:如果传入的两个参数不为null, 会出现什么情况?答:所有默认callback都被"穿透性"屏蔽
  24. }
  25. }
  26. class Operation//Operation包含一些对订单的操作
  27. {
  28. public Action DefaultSuccessCallback { get; set; }//默认成功回调
  29. public Action DefaultFailureCallback { get; set; }//默认失败回调
  30. public Operation InnerOperation { get; set; }//Operation类里面还有Operation,即可以套娃
  31. public object Operate(object input,Action successCallback,Action failureCallback)
  32. {//两次判断作用为潘丹传入回调是否为空,为空就是用默认回调替代传入的回调
  33. if(successCallback==null)
  34. {
  35. successCallback=this.DefaultSuccessCallback;
  36. }
  37. if(failureCallback==null)
  38. {
  39. failureCallback=this.DefaultFailureCallback;
  40. }
  41. //由于不清楚上面传入的回调是否被替代,try...catch...内传入innnerOperation的回调难以确认
  42. object result = null;
  43. try
  44. {
  45. result = this.InnerOperation.Operate(input,successCallback,failureCallback);
  46. }
  47. catch
  48. {
  49. failureCallback.Invoke();
  50. }
  51. successCallback.Invoke();
  52. return result;
  53. }
  54. }
  55. }

委托的高级使用

多播(multicast)委托

多播委托即一个委托内部封装不止一个方法。

  1. using System;
  2. using System.Threading;
  3. namespace DelegateExample
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. student stu1 = new Student{} { ID = 1, PenColor = ConsoleColor.Yellow };
  10. student stu2 = new Student{} { ID = 2, PenColor = ConsoleColor.Green };
  11. student stu3 = new Student{} { ID = 3, PenColor = ConsoleColor.Red };
  12. action action1 = new Action(stu1.DoHomework);
  13. action action2 = new Action(stu2.DoHomework);
  14. action action3 = new Action(stu3.DoHomework);
  15. // 单播委托
  16. //action1.Invoke();
  17. //action2.Invoke();
  18. //action3.Invoke();
  19. // 多播委托
  20. action1 += action2;
  21. action1 += action3;
  22. action1.Invoke();
  23. }
  24. }
  25. class Student
  26. {
  27. public int ID { get; set; }
  28. public ConsoleColor PenColor { get; set; }
  29. public void DoHomework()
  30. {
  31. for (int i = 0; i < 5; i++)
  32. {
  33. Console.ForegroundColor = this.PenColor;
  34. Console.WriteLine("Student {0} doing homework {1} hour(s)", ID, i);
  35. Thread.Sleep(1000);
  36. }
  37. }
  38. }
  39. }

隐式异步调用

QQ截图20220302140034.png

同步与异步的简介

  • 中英文的语言差异
  • 同步:你做完了我(在你的基础上)接着做
  • 异步:咱们两个同时做(相当于汉语中的”同步进行”)

    异步互不相干: 这里说的“互不相干”指的是逻辑上,而现实工作当中经常会遇到多个线程共享(即同时访问)同一个资源(比如某个变量)的情况,这时候如果处理不当就会产生线程间争夺资源的冲突。

同步调用与异步调用的对比

  • 每一个运行的程序是一个进程(process)
  • 每个进程可以有一个或者多个线程(thread)
  • 同步调用是在同一线程内
  • 异步调用的底层机理是多线程
  • 串行==同步==单线程,并行==异步==多线程

    三种同步调用

    ```csharp using System; using System.Threading;

namespace DelegateExample { class Program { static void Main(string[] args) { Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow }; Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green }; Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };

  1. // 直接同步调用
  2. //stu1.DoHomework();
  3. //stu2.DoHomework();
  4. //stu3.DoHomework();
  5. Action action1 = new Action(stu1.DoHomework);
  6. Action action2 = new Action(stu2.DoHomework);
  7. Action action3 = new Action(stu3.DoHomework);
  8. // 间接同步调用
  9. //action1.Invoke();
  10. //action2.Invoke();
  11. //action3.Invoke();
  12. // 多播委托,同步调用
  13. action1 += action2;
  14. action1 += action3;
  15. action1.Invoke();
  16. // 主线程模拟在做某些事情。
  17. for (var i = 0; i < 10; i++)
  18. {
  19. Console.ForegroundColor = ConsoleColor.Cyan;
  20. Console.WriteLine("Main thread {0}", i);
  21. Thread.Sleep(1000);
  22. }
  23. }
  24. }
  25. class Student
  26. {
  27. public int ID { get; set; }
  28. public ConsoleColor PenColor { get; set; }
  29. public void DoHomework()
  30. {
  31. for (int i = 0; i < 5; i++)
  32. {
  33. Console.ForegroundColor = PenColor;
  34. Console.WriteLine("Student {0} doing homework {1} hour(s)", ID, i);
  35. Thread.Sleep(1000);
  36. }
  37. }
  38. }

}

  1. 三种同步调用的结果一样:<br />![](https://cdn.nlark.com/yuque/0/2018/png/101969/1538890351212-bd09016d-62e6-47b9-acca-60366ad4d398.png#crop=0&crop=0&crop=1&crop=1&from=url&id=xJ1d6&margin=%5Bobject%20Object%5D&originHeight=477&originWidth=327&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  2. <a name="MdhlN"></a>
  3. ### 隐式多线程v.S.显式多线程
  4. - 直接同步调用:使用方法名
  5. - 间接同步调用:使用单播/多播委托的Invoke方法
  6. - 隐式异步调用:使用委托的Begin Invoke
  7. - 显式斤步调用:使用ThreadTask
  8. <a name="NiEbA"></a>
  9. #### 使用委托进行隐式异步调用 BeginInvoke
  10. ```csharp
  11. using System;
  12. using System.Threading;
  13. namespace DelegateExample
  14. {
  15. class Program
  16. {
  17. static void Main(string[] args)
  18. {
  19. Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
  20. Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
  21. Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
  22. Action action1 = new Action(stu1.DoHomework);
  23. Action action2 = new Action(stu2.DoHomework);
  24. Action action3 = new Action(stu3.DoHomework);
  25. // 使用委托进行隐式异步调用。
  26. // BeginInvoke 自动生成分支线程,并在分支线程内调用方法。
  27. action1.BeginInvoke(null, null);
  28. action2.BeginInvoke(null, null);
  29. action3.BeginInvoke(null, null);
  30. // 主线程模拟在做某些事情。
  31. for (var i = 0; i < 10; i++)
  32. {
  33. Console.ForegroundColor = ConsoleColor.Cyan;
  34. Console.WriteLine("Main thread {0}", i);
  35. Thread.Sleep(1000);
  36. }
  37. }
  38. }
  39. class Student
  40. {
  41. public int ID { get; set; }
  42. public ConsoleColor PenColor { get; set; }
  43. public void DoHomework()
  44. {
  45. for (int i = 0; i < 5; i++)
  46. {
  47. Console.ForegroundColor = PenColor;
  48. Console.WriteLine("Student {0} doing homework {1} hour(s)", ID, i);
  49. Thread.Sleep(1000);
  50. }
  51. }
  52. }
  53. }

19委托详解 - 图4

使用 Thread 与 Task 进行异步调用

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. namespace DelegateExample
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. Student stu1 = new Student { ID = 1, PenColor = ConsoleColor.Yellow };
  12. Student stu2 = new Student { ID = 2, PenColor = ConsoleColor.Green };
  13. Student stu3 = new Student { ID = 3, PenColor = ConsoleColor.Red };
  14. // 老的显式异步调用方式 Thread
  15. Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
  16. Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
  17. Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
  18. thread1.Start();
  19. thread2.Start();
  20. thread3.Start();
  21. // 使用 Task
  22. Task task1 = new Task(new Action(stu1.DoHomework));
  23. Task task2 = new Task(new Action(stu2.DoHomework));
  24. Task task3 = new Task(new Action(stu3.DoHomework));
  25. /*当未引用名称空间时,使用“Ctrl+.”+Enter进行引用*/
  26. task1.Start();
  27. task2.Start();
  28. task3.Start();
  29. // 主线程模拟在做某些事情。
  30. for (var i = 0; i < 10; i++)
  31. {
  32. Console.ForegroundColor = ConsoleColor.Cyan;
  33. Console.WriteLine("Main thread {0}", i);
  34. Thread.Sleep(1000);
  35. }
  36. }
  37. }
  38. class Student
  39. {
  40. public int ID { get; set; }
  41. public ConsoleColor PenColor { get; set; }
  42. public void DoHomework()
  43. {
  44. for (int i = 0; i < 5; i++)
  45. {
  46. Console.ForegroundColor = PenColor;
  47. Console.WriteLine("Student {0} doing homework {1} hour(s)", ID, i);
  48. Thread.Sleep(1000);
  49. }
  50. }
  51. }
  52. }

19委托详解 - 图5

设计模式常识:适时地使用接口(interface)取代委托

Java 完全使用接口取代了委托功能。

以前面的模板方法举列,通过接口也能实现方法的可替换。

  1. using System;
  2. namespace DelegateExample
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. IProductFactory pizzaFactory = new PizzaFactory();
  9. IProductFactory toyCarFactory = new ToyCarFactory();
  10. var wrapFactory = new WrapFactory();
  11. Box box1 = wrapFactory.WrapProduct(pizzaFactory);
  12. Box box2 = wrapFactory.WrapProduct(toyCarFactory);
  13. Console.WriteLine(box1.Product.Name);
  14. Console.WriteLine(box2.Product.Name);
  15. }
  16. }
  17. interface IProductFactory
  18. {
  19. Product Make();
  20. }
  21. class PizzaFactory : IProductFactory
  22. {
  23. public Product Make()
  24. {
  25. var product = new Product();
  26. product.Name = "Pizza";
  27. return product;
  28. }
  29. }
  30. class ToyCarFactory : IProductFactory
  31. {
  32. public Product Make()
  33. {
  34. var product = new Product();
  35. product.Name = "Toy Car";
  36. return product;
  37. }
  38. }
  39. class Product
  40. {
  41. public string Name { get; set; }
  42. }
  43. class Box
  44. {
  45. public Product Product { get; set; }
  46. }
  47. class WrapFactory
  48. {
  49. // 模板方法,提高复用性
  50. public Box WrapProduct(IProductFactory productFactory)
  51. {
  52. var box = new Box();
  53. Product product = productFactory.Make();
  54. box.Product = product;
  55. return box;
  56. }
  57. }
  58. }