第19节 委托详解(还不怎么理解)

  • 什么是委托
  • 委托的声明(自定义委托)
  • 委托的一般使用
  • 委托的高级使用

什么是委托

  1. 委托(delegate)是函数指针的“升级版”
    实例:C/C++中的函数指针
#include<stdio.h>//函数指针数据类型
//Calc 是函数指针类型的名字
//两个整数类型的参数 a,b
//返回整数类型的值
typedef int(*Calc)(int a, int b);  int Add(int x, int y)
{int result = x + y;return result;
}int Sub(int x, int y)
{int result = x - y;return result;
}int main()
{int x = 100;int y = 200;int z = 0;//声明函数指针类型的变量Calc funcPoint1 = &Add;  Calc funcPoint2 = &Sub;z = funcPoint1(x, y);printf("%d + %d = %d\n", x, y, z);z = funcPoint2(x, y);printf("%d - %d = %d\n", x, y, z);
}
  1. 一切皆地址
    1) 变量(数据)是以某个地址(变量名所对应的内存地址)为起点的一段内存中所存储的值;
    2)函数(算法)是以某个地址(函数名所对应的内存地址)为起点的一段内存中所存储的一组机器语言指令。

  2. 直接调用与间接调用
    1)直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行 —> 返回;
    2)间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行 —> 返回。

  3. Java 中没有与委托相对应的功能实体

  4. 委托的简单使用
    1)Action 委托
    2)Func 委托

class Program
{static void Main(string[] args){Calculator cal = new Calculator();//Action 类型的委托,只能委托返回值为void的函数Action action = new Action(cal.Report);cal.Report();  //直接调用action.Invoke();  //使用委托间接调用action();  //简便书写//Func 类型的委托,可以委托各种返回值的函数//<>里面写函数的参数类型和返回值类型Func<int, int, int> func1 = new Func<int, int, int>(cal.Add);Func<int, int, int> func2 = new Func<int, int, int>(cal.Sub);int x = 100;int y = 200;int z = 0;z = func1.Invoke(x, y);Console.WriteLine(z);z = func2.Invoke(x, y);Console.WriteLine(z);}
}class Calculator
{public void Report(){Console.WriteLine("I have 3 methods!");}public int Add(int a, int b){int result = a + b;return result;}public int Sub(int a, int b){int result = a - b;return result;}
}

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

  1. 委托是一种类(class),类是数据类型,所以委托也是一种数据类型
  2. 它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
  3. 注意声明委托的位置
    避免写错地方结果声明成嵌套类型。
    委托在名称空间体里进行声明,和其他类处在同一级别。如果在Program类里面声明委托Calc,则Calc是Program的嵌套类,在使用时需写成Program.Calc。
  4. 委托与所封装的方法必须“类型兼容”
    下图的第一行是委托类型的声明,而后面四行是委托可以封装的方法。

注:返回值的数据类型一致;参数列表在个数和数据类型上一致(参数名不需要一样)

//声明自定义委托类型
//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
public delegate double Calc(double x, double y);  class Program
{static void Main(string[] args){Calculator calculator = new Calculator();Calc calc1 = new Calc(calculator.Add);Calc calc2 = new Calc(calculator.Sub);Calc calc3 = new Calc(calculator.Mul);Calc calc4 = new Calc(calculator.Div);double a = 10;double b = 2;double c = 0;c = calc1.Invoke(a, b);  //或者像函数一样直接调用,c = calc1(a, b)Console.WriteLine("{0} + {1} = {2}", a, b, c);c = calc2.Invoke(a, b);Console.WriteLine("{0} - {1} = {2}", a, b, c);c = calc3.Invoke(a, b);Console.WriteLine("{0} * {1} = {2}", a, b, c);c = calc4.Invoke(a, b);Console.WriteLine("{0} / {1} = {2}", a, b, c);}
}class Calculator
{public double Add(double a, double b){return a + b;}public double Sub(double a, double b){return a - b;}public double Mul(double a, double b){return a * b;}public double Div(double a, double b){return a / b;}
}

委托的一般使用

  1. 实例:把方法当做参数传给另一个方法
    1)正确使用1:模板方法,“借用”指定的外部方法来产生结果。
    相当于“填空题”
    常位于代码中部
    委托返回值
    2) 正常使用2:回调(callback)方法,调用指定的外部方法。
    相当于“流水线”
    常位于代码末尾
    委托返回值

举例:
模板方法

//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
public delegate double Calc(double x, double y);class Program
{static void Main(string[] args){ProductFactory productFactory = new ProductFactory();WrapFactory wrapFactory = new WrapFactory();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);Box box1 = wrapFactory.WrapProduct(func1);Box box2 = wrapFactory.WrapProduct(func2);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}
}class Product
{public string Name { get; set; }  //每个产品都有自己的名字
}class Box
{public Product Product { get; set; }  //每个箱子里包装一个产品
}class WrapFactory  //负责将产品包上盒子交给客户
{//模板方法//接收一个委托类型的参数,委托方法的返回值类型为Productpublic Box WrapProduct(Func<Product> getProduct){Box box = new Box();Product product = getProduct.Invoke();box.Product = product;return box;}
}class ProductFactory
{public Product MakePizza(){Product product = new Product();product.Name = "Pizza";return product;}public Product MakeToyCar(){Product product = new Product();product.Name = "Toy Car";return product;}
}

回调方法

//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
public delegate double Calc(double x, double y);class Program
{static void Main(string[] args){ProductFactory productFactory = new ProductFactory();WrapFactory wrapFactory = new WrapFactory();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);Logger logger = new Logger();Action<Product> log = new Action<Product>(logger.Log);Box box1 = wrapFactory.WrapProduct(func1, log);Box box2 = wrapFactory.WrapProduct(func2, log);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}
}class Product
{public string Name { get; set; }  //每个产品都有自己的名字public double Price { get; set; }
}class Logger  //记录程序运行的状态
{public void Log(Product product){//DateTime.UtcNow 是不带时区的时间Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);}
}class Box
{public Product Product { get; set; }  //每个箱子里包装一个产品
}class WrapFactory  //负责将产品包上盒子交给客户
{//模板方法//接收一个委托类型的参数,委托方法的返回值类型为Productpublic Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback)  //没有返回值的方法,使用 Action 委托{Box box = new Box();Product product = getProduct.Invoke();if (product.Price >= 50){logCallback(product);}box.Product = product;return box;}
}class ProductFactory
{public Product MakePizza(){Product product = new Product();product.Name = "Pizza";product.Price = 12;return product;}public Product MakeToyCar(){Product product = new Product();product.Name = "Toy Car";product.Price = 100;return product;}
}
  1. 注意:难精通 + 易使用 + 功能强大的东西,一旦被滥用则后果非常严重
    1)缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎;
    2)缺点2:使可读性下降、debug的难度增加;
    3)缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护;
    4)缺点4:委托使用不当有可能造成内存泄漏和程序性能下降。

委托的高级使用

  1. 多播(multicast)委托
    一个委托内部封装不止一个方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; //与多线程相关namespace MulticastDelegateExample
{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 };Action action1 = new Action(stu1.DoHomework);Action action2 = new Action(stu2.DoHomework);Action action3 = new Action(stu3.DoHomework);action1.Invoke();  //一个委托封装一个方法(单播委托)action2.Invoke();action3.Invoke();//一个委托封装多个方法(多播委托)//执行顺序与封装顺序一致,action1、action2、action3action1 += action2;  action1 += action3;action1.Invoke();}}class Student{public int ID { get; set; }public ConsoleColor PenColor { get; set; }  //ConsoleColor 是屏幕显示结果时字体的颜色public void DoHomework(){for (int i = 0; i<5; i++){Console.ForegroundColor = this.PenColor;Console.WriteLine("Student {0} doing homework {1} hours(s).",this.ID,i);Thread.Sleep(1000);}}}
}
  1. 隐式异步调用

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

2)同步调用与异步调用的对比
(1)每一个运行的程序是一个进程(process)
(2)每个进程可以有一个或多个线程(thread),第一个运行的线程是主线程
(3)同步调用是在同一线程内
(4)异步调用的底层机理是多线程
(5)串行----同步----单线程,并行----异步----多线程

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

3)隐式多线程 v.s. 显式多线程
(1)直接同步调用:使用方法名

stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();

(2)间接同步调用:使用单播/多播委托的 Invoke 方法

Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);//单播委托的间接同步调用,Invoke方法是同步调用
action1.Invoke();
action2.Invoke();
action3.Invoke();//多播委托的间接同步调用
action1 += action2;
action1 += action3;
action1.Invoke();

(3)隐式异步调用:使用委托的 BeginInvoke

Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);//隐式的间接异步调用(最最重要)
//BeginInvoke方法会自动地生成一个分支线程,在分支线程里调用封装的方法
//参数1:异步调用的回调,在分支线程里调用完方法之后的后续操作,若无操作,则为null
//参数2:一般为null
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);

(4)显式异步调用:使用 Thread 或 Task

//Thread 显式的异步调用
Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));thread1.Start();
thread2.Start();
thread3.Start();//Task 显式的异步调用(using System.Threading.Tasks;)
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));task1.Start();
task2.Start();
task3.Start();

完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;  //Tasks
using System.Threading;  //与多线程相关namespace MulticastDelegateExample
{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 };//直接同步调用(直接调用是用方法的名字调用该方法)stu1.DoHomework();stu2.DoHomework();stu3.DoHomework();Action action1 = new Action(stu1.DoHomework);Action action2 = new Action(stu2.DoHomework);Action action3 = new Action(stu3.DoHomework);//单播委托的间接同步调用,Invoke方法是同步调用action1.Invoke();action2.Invoke();action3.Invoke();//多播委托的间接同步调用action1 += action2;action1 += action3;action1.Invoke();//隐式的间接异步调用(最最重要)//BeginInvoke方法会自动地生成一个分支线程,在分支线程里调用封装的方法//参数1:异步调用的回调,在分支线程里调用完方法之后的后续操作,若无操作,则为null//参数2:一般为nullaction1.BeginInvoke(null, null);action2.BeginInvoke(null, null);action3.BeginInvoke(null, null);//Thread 显式的异步调用Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));thread1.Start();thread2.Start();thread3.Start();//Task 显式的异步调用(using System.Threading.Tasks;)Task task1 = new Task(new Action(stu1.DoHomework));Task task2 = new Task(new Action(stu2.DoHomework));Task task3 = new Task(new Action(stu3.DoHomework));task1.Start();task2.Start();task3.Start();for (int i = 0; i < 10; i++){Console.ForegroundColor = ConsoleColor.Cyan;Console.WriteLine("Main thread {0}.",i);Thread.Sleep(1000);}}}class Student{public int ID { get; set; }public ConsoleColor PenColor { get; set; }  //ConsoleColor 是屏幕显示结果时字体的颜色public void DoHomework(){for (int i = 0; i<5; i++){Console.ForegroundColor = this.PenColor;Console.WriteLine("Student {0} doing homework {1} hours(s).",this.ID,i);Thread.Sleep(1000);}}}
}

异步调用会出现资源冲突

  1. 应该适时地使用接口(interface)取代一些对委托的使用
    Java 完全地使用接口取代了委托的功能,即 Java 没有 C# 中委托相对应的功能实体
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DelegateExample
{class Program{static void Main(string[] args){IProductFactory pizzaFactory = new PizzaFactory();IProductFactory toycarFactory = new ToyCarFactory();WrapFactory wrapFactory = new WrapFactory();Box box1 = wrapFactory.WrapProduct(pizzaFactory);Box box2 = wrapFactory.WrapProduct(toycarFactory);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}interface IProductFactory{Product Make();}class PizzaFactory : IProductFactory{public Product Make(){Product product = new Product();product.Name = "Pizza";return product;}}class ToyCarFactory : IProductFactory{public Product Make(){Product product = new Product();product.Name = "Toy Car";return product;}}class Product{public string Name { get; set; }  //每个产品都有自己的名字}class Box{public Product Product { get; set; }  //每个箱子里包装一个产品}class WrapFactory  //负责将产品包上盒子交给客户{public Box WrapProduct(IProductFactory productFactory)  {Box box = new Box();Product product = productFactory.Make();box.Product = product;return box;}}
}

第 19 节 委托详解相关推荐

  1. JavaScript(js)事件冒泡、事件捕获、事件委托详解

    JavaScript(js)事件冒泡.事件捕获.事件委托详解 1.什么是事件 JavaScript和HTML之间的交互是通过事件实现的.事件,就是文档或浏览器窗口发生的一些特定的交互瞬间.可以使用监听 ...

  2. 第 20、21、22节 事件详解

    第20.21.22节 事件详解.Linq 详解 初步了解事件 事件的应用 事件的声明 事件与委托的关系 初步了解事件 事件的功能 = 通知 + 可选的事件参数(即详细信息) 定义:单词 Event,译 ...

  3. python代码案例详解-第7.20节 案例详解:Python抽象类之真实子类

    第7.20节 案例详解:Python抽象类之真实子类 上节介绍了Python抽象基类相关概念,并介绍了抽象基类实现真实子类的步骤和语法,本节结合一个案例进一步详细介绍. 一. 案例说明 本节定义了图形 ...

  4. 基础拾遗------委托详解

    基础拾遗: 基础拾遗------特性详解 基础拾遗------webservice详解 基础拾遗------redis详解 基础拾遗------反射详解 基础拾遗------委托详解 基础拾遗---- ...

  5. mysql压缩包安装教程8.0.19,win10安装zip版MySQL8.0.19的教程详解

    win10安装zip版MySQL8.0.19的教程详解 一. 下载后解压到想安装的目录 二. 在安装目录中添加配置文件my.ini [mysqld] # 设置3306端口 port=3306 # 设置 ...

  6. mysql8.0日期类型_mysql8.0.19基础数据类型详解

    mysql基础数据类型 mysql常用数据类型概览 ![1036857-20170801181433755-146301178](D:\笔记\mysql\复习\1036857-201708011814 ...

  7. MySQL-5.7.19版本安装详解

    MySQL-5.7.19版本安装详解 1. 软件下载 Mysql5.7地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19-win ...

  8. Csharp委托详解

    参考视频 c#教程 using System; using System.Collections.Generic; using System.Linq; using System.Text; name ...

  9. C#语言入门详解笔记(9)—P19 委托详解

    C#语言入门详解_哔哩哔哩_bilibiliC#语言入门详解搬运,随youtube上进度更新.刘老师的C#视频教程到30集就告一段落了,感谢刘老师在这5年间的付出.能上youtube的同学可以去刘老师 ...

最新文章

  1. postman使用介绍
  2. Android设置布局位置五等分,五等分划分屏幕
  3. 构建安全的Xml Web Service系列之初探使用Soap头
  4. java 容器_JAVA的容器
  5. JS document.execCommand实现复制功能
  6. STL运用的C++技术(6)——函数对象
  7. 北京公交公开招标思路
  8. linux hg(mercurial)入门
  9. IIS由于出现权限不足而无法读取配置文件解决方案
  10. Dagger2的使用示例
  11. 《Python编程:从入门到实践》学习笔记——第5章 if语句
  12. Linux系统管理(一)基础管理
  13. List集合 值进行累加
  14. opencv-python 在图像上打印英文和中文字符
  15. android系统 最新版本是多少,安卓系统最新版本是多少 Android M 最新功能
  16. 牛逼的python代码_几段牛逼的 Python 代码理解面向对象
  17. 娱乐弹弹弹——程序猿眼中的女人
  18. java中引用数据类型有哪几种
  19. 闭锁CountDownLatch
  20. 第五期_信息收集《Metasploit Unleashed Simplified Chinese version(Metasploit官方文档教程中文版)》

热门文章

  1. 经纬度计算丢失经度计算
  2. backdoor-factory详细使用教程
  3. 航空公司规定退改签查询-机票退改签查询接口
  4. mysql安装忘记初始密码_MySql安装忘记初始密码解决方法
  5. java中collection框架应用示例:学生选课系统
  6. airpods二代降噪吗_传苹果明年将推两款无线耳机:AirPods Pro二代来了_移动互联网...
  7. 基于UNet网络实现的人像分割 | 附数据集
  8. U盘启动ubuntu出现黑屏下划线
  9. 软件测试自学简单吗?如何自学?
  10. 0基础也能学!软件测试自学路线(附带学习资料)