委托(delegate)是函数指针的“升级版”。C/C++中的函数指针。

C语言中的函数指针:

#include<stdio.h>// 声明函数指针,定义为一种数据类型。
typedef int (*Calc)(int a, int b);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;// 直接调用z = Add(x, y);printf("%d+%d=%d\n", x, y, z);z = Sub(x, y);printf("%d-%d=%d\n", x, y, z);// 声明函数指针类型的变量Calc funcPointer1 = &Add;Calc funcPointer2 = &Sub;//通过函数指针来调用函数z = funcPointer1(x, y);printf("%d+%d=%d\n", x, y, z);z = funcPointer2(x, y);printf("%d-%d=%d\n", x, y, z);system("pause");return 0;
}

看完代码觉得好多余呀,绕来绕去,直接用不好吗?我也有此感触:Python中都是直接把函数作为变量传递的。

def Add(a, b):return a+ba = Add
print(a(2,3))

咱们捺下性子继续:

委托:按照字面意思:一件事情,我不亲自去做,而是让别人去代办,替我去做。间接去完成事情。比如上文,定义了函数,自己不干所属的功能任务,让别人去干这事。(比如:让新线程去做这事)。

谁调用这个函数方法,谁干这事。



一切皆地址

  • 变量(数据)是以某个地址为起点的一段内存中所存储的
  • 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令

程序的本质,就是:数据+算法。数据存储在变量中,或者说变量代表着数据。函数代表的是算法。

变量的本质: 是以变量名所对应的内存地址起点的一段内存,内存中存储的就是变量的数据。这段内存有多大,是由变量的数据类型所决定的。所以说:变量是地址

函数的本质,是以函数名所对应的内存地址为起点的一段内存,在这段内存中,存储的不是某个值,而是一组机器语言指令。CPU就是按照这组指令一条条的去执行,完成函数所包含的算法。

综上,无论是数据还是算法,都是保存在内存地址中的。

变量用来寻找数据的地址。

函数用来用来寻找算法的地址。



直接调用与间接调用

直接调用:通过函数名来调用函数:CPU通过函数名直接获得函数所在地址并开始执行➨➨➨返回调用者。

间接调用:通过函数指针来调用函数:CPU通过读取指向某个函数的函数指针存储的值获得函数所在地址并开始执行➨➨➨返回调用者。

直接调用和间接调用,效果是完全一样的。


委托是函数指针的升级

class Program
{static void Main(string[] args){Calculator calculator = new Calculator();Action action = new Action(calculator.Report); // Report 后面没有圆括号。只要方法名,不要调用。// 直接调用calculator.Report();// 间接调用1:action.Invoke();// 间接调用2:action();//Action要求,返回值为空、且参数为空的函数。// 带参数,带返回值类型的委托调用方式。Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);int x = 100;int y = 200;int z = 0;//z = func1.Invoke(x, y);z = func1(x, y);Console.WriteLine(z);//z = func2.Invoke(x, y);z = func2(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;}
}

Action和Function是CSharp中为我们准备好的委托。


自定义委托

委托是一种类,类是一种数据类型,而且是引用类型数据类型。用类我们可以声明变量,创建实例。

Type t = typeof(Action);
Console.WriteLine(t.IsClass);

委托是一种类,所以委托的声明要在名称空间体里面:这样它跟Program类是平级的。

public[公开的] delegate[声明一个委托] double[目标方法的返回值类型Calc[创造的委托的类型](double x,double y)[要写上目标方法的参数列表];[最后写上分号,完成一个语句]

public delegate double Calc(double x, double y);

自定义委托类型声明好了。

using System;
using System.Collections;
using System.Collections.Generic;namespace test
{// 定义委托类型。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 = 100;double b = 200;double c = 0;c = calc1(a, b);Console.WriteLine(c);c = calc2(a, b);Console.WriteLine(c);c = calc3(a, b);Console.WriteLine(c);c = calc4(a, b);Console.WriteLine(c);}}class Calculator{public double Add(double x, double y){return x + y;}public double Sub(double x, double y){return x - y;}public double Mul(double x, double y){return x * y;}public double Div(double x, double y){return x / y;}}
}


委托的一般使用

方法当做参数传给另一个方法。

第一种情况:模板方法:“借用”指定的外部方法来产生结果。

  • 模板方法的最好例子是通用的排序算法,需要调用特定类型的比较算法来确定排序,此时将特定类型的比较算法作为委托参数传递给通用排序算法。

  • 我们自己写了一个方法,这个方法中有一处是不确定的,其余部分都是确定好的。不确定的部分,就需要依靠委托类型的参数(所包含的方法来填补)去实现。

  • 相当于“选填”。

  • 常位于代码中部。

  • 委托有返回值。

第二种方法:回调方法:调用指定的外部方法。

  • callback:调用一些功能,解决一些问题。某些方法,可以调用,也可以不调用,用到了就调用,用不到就不调用。

  • 相当于“流水线”。

  • 常位于代码末尾。

  • 委托无返回值。

模板方法:

好处是:Product类、Box类、WrapFactory类都不用再动了。只需要不停的去拓展产品工厂就可以了。独立性更强。

不管工厂ProductFactory类生产什么产品,只要把生产产品的方法封装到一个委托类型对象里面传给模板方法,模板方法就一定能够把产品Product类包装好,返回一个箱子Box类。

Reuse:重复使用,复用。提高工作效率,减少bug的引入。

class Program
{static void Main(string[] args){// 工厂类的实例,为后续生产准备ProductFactory productFactory = new ProductFactory();// 包装类的工厂实例。WrapFactory wrapFactory = new WrapFactory();// 模板类型方法,准备委托类型的参数。封装加工厂中的生产pizza的方法。// 间接调用。绕过了方法拥有者,直接调用方法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
{// 模板方法!!!!!!!!!!!public 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;}
}

回调方法:

class Program
{static void Main(string[] args){// 工厂类的实例,为后续生产准备ProductFactory productFactory = new ProductFactory();// 包装类的工厂实例。WrapFactory wrapFactory = new WrapFactory();// 模板类型方法,准备委托类型的参数。封装加工厂中的生产pizza的方法。// 间接调用。绕过了方法拥有者,直接调用方法Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);// 声明Logger类实例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 Logger
{// 把log方法以回调方法的形式传入模板方法中。public void Log(Product product){//不带时区的时间。Console.WriteLine("Product'{0}' created at {1}.Price is {2}.", product.Name, DateTime.UtcNow, product.Price);}
}class Product
{public string Name { get; set; }public double Price { get; set; }
}class Box
{public Product Product { get; set; }
}class WrapFactory
{// 模板方法!!!!!!!!!!!public Box WrapProduct(Func<Product> getProduct,Action<Product> logCallBack){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;}
}

Log是没有返回值的方法,选择Action类型的委托。Action委托可以接受类型参数,Log方法接受的是Product类型的参数,所以这里接受的也是Product类型的参数。

然后添加逻辑,来决定是否调用Log。条件:如果一个产品价格大于50元,就Log一下。


先把类全部实例化,再把委托全部创建出来,类似指针函数的赋值,然后调用。

都是用委托类型的参数,封装了一个外部的方法,然后把方法传进另外一个方法内部,在进行间接调用。


委托的缺点:难精通+易使用+功能强大的东西,一旦被滥用则后果非常严重。

缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎。

缺点2:使可读性下降、Debug的难度增加。

缺点3:灾难性:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以维护和阅读。

缺点4:委托使用不当有可能造成内存泄漏和程序性能下降。


委托的高级使用

多播Multicast委托:

一个委托里面封装的不止一个方法。

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();action2();action3();//多播委托:一个委托,封装多个方法。多播委托。调用顺序是根据添加顺序来执行的。action1 += action2;action1 += action3;action1();}
}class Student
{public int ID { get; set; }public ConsoleColor PenColor { get; set; }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(500);}}
}

隐式异步调用

同步:两个人做事:你做完了,我在你基础上接着做。你没做完,我干等着,啥也不干。

异步:咱们两个同时做,你做你的,我做我的,各做各的。

每一个运行的程序都是一个进程(Process),一个进程可以有一个或者多个线程(Thread),第一个启动的线程叫做主线程。

同步调用是在同一个线程内,异步调用的底层机理是多线程

串行==同步==单线程

并行==异步==多线程

直接同步调用

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();for (int i = 0; i < 10; i++)
{Console.ForegroundColor = ConsoleColor.Cyan;Console.WriteLine("Main Thread {0}.",i);Thread.Sleep(500);
}

间接同步调用

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();
action2();
action3();
// 多播委托
action1 += action2;
action1 += action3;
action1();for (int i = 0; i < 10; i++)
{Console.ForegroundColor = ConsoleColor.Cyan;Console.WriteLine("Main Thread {0}.",i);Thread.Sleep(300);
}

多播委托也是同步调用。


隐式异步调用

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.BeginInvoke(null, null);  // 生成分支线程,然后分支线程去调用它封装的方法。要求回调方法
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);for (int i = 0; i < 10; i++)
{Console.ForegroundColor = ConsoleColor.Cyan;Console.WriteLine("Main Thread {0}.", i);Thread.Sleep(500);
}

显示异步调用

自己动手生成多线程。

一种原始方式是使用Thread

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 };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();for (int i = 0; i < 10; i++)
{Console.ForegroundColor = ConsoleColor.Cyan;Console.WriteLine("Main Thread {0}.", i);Thread.Sleep(500);
}
Console.ReadLine();

更高级的方式是使用Task

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 };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(500);
}
// Task.Run(()=>stu1.DoHomework())   // 异步调用。
Console.ReadLine();

提醒:

应该适时地使用接口(interface)取代一些对委托的使用。

Java完全地使用接口取代了了委托的功能。即:Java没有与C#中相对应的功能实体。

代码重构如下:不再需要ProductFactory类。

class Program
{static void Main(string[] args){// 工厂类的实例,为后续生产准备IProductFactory pizzaFactory = new PizzaFactory();IProductFactory toyCarFactory = new ToyCarFactory();// 包装类的工厂实例。WrapFactory wrapFactory = new WrapFactory();// 声明Logger类实例Logger logger = new Logger();Box box1 = wrapFactory.WrapProduct(pizzaFactory,logger);Box box2 = wrapFactory.WrapProduct(toyCarFactory, logger);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";product.Price = 12;return product;}
}class ToyCarFactory : IProductFactory
{public Product Make(){Product product = new Product();product.Name = "Toy Car";product.Price = 100;return product;}
}class Logger
{// 把log方法以回调方法的形式传入模板方法中。public void Log(Product product){//不带时区的时间。Console.WriteLine("Product'{0}' created at {1}.Price is {2}.", product.Name, DateTime.UtcNow, product.Price);}
}class Product
{public string Name { get; set; }public double Price { get; set; }
}class Box
{public Product Product { get; set; }
}class WrapFactory
{// 模板方法,不再需要委托类型的参数,需要工厂类型的参数。public Box WrapProduct(IProductFactory productFactory, Logger logCallBack){Box box = new Box();Product product = productFactory.Make();if (product.Price>=50){logCallBack.Log(product);}box.Product = product;return box;}
}

不再有方法级别的耦合。

C#学习笔记(十三)委托——一切皆地址相关推荐

  1. OpenGL学习笔记(十三):将纹理贴图应用到四边形上,对VAO/VBO/EBO/纹理/着色器的使用方式进行总结

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7919 ...

  2. Windows内存管理学习笔记(一)—— 线性地址的管理

    Windows内存管理学习笔记(一)-- 线性地址的管理 用户空间线性地址的管理 实验一:理解用户空间线性地址管理 Private Memory 实验二:理解Private Memory 堆 实验三: ...

  3. Mcad学习笔记之委托再理解(delegate的构造器,MulticastDelegate,BeginInvoke,EndInvoke,Invoke4个方法的探讨)...

    相关文章导航 Sql Server2005 Transact-SQL 新兵器学习总结之-总结 Flex,Fms3相关文章索引 FlexAir开源版-全球免费多人视频聊天室,免费网络远程多人视频会议系统 ...

  4. Polyworks脚本开发学习笔记(十三)-深入了解MACRO命令

    Polyworks脚本开发学习笔记(十三)-深入了解MACRO命令 MACRO命令中包含了很多宏脚本管理以及变量操作命令,交互操作命令等,是非常重要的一个模块. 数组和字符串操作 从数组中拿掉一个元素 ...

  5. java学习笔记十三

    11. 凡是继承了FilterOutputStream或FilterInputStream的类都是过滤流,也就是说他们不能直接跟目标(键盘,文件,网络等,节点流可以)数据打交道,只能包装 Intput ...

  6. Mr.J-- jQuery学习笔记(十三)--选项Tab卡

    页面渲染 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8 ...

  7. [读书笔记]C#学习笔记二: 委托和事件的用法及不同.

    前言:  C#委托是什么 c#中的委托可以理解为函数的一个包装, 它使得C#中的函数可以作为参数来被传递, 这在作用上相当于C++中的函数指针. C++用函数指针获取函数的入口地址, 然后通过这个指针 ...

  8. 吴恩达《机器学习》学习笔记十三——机器学习系统(补充)

    这次笔记是对笔记十二的补充,之前讨论了评价指标,这次主要是补充机器学习系统设计中另一个重要的方面,用来训练的数据有多少的问题. 笔记十二地址:https://blog.csdn.net/qq_4046 ...

  9. java之jvm学习笔记十三(jvm基本结构)

    欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完 ...

最新文章

  1. web前端 react与vue 流行框架的比较
  2. mac mysql 5.7.9安装教程_mac系统OS X10.10版本安装最新5.7.9mysql的方法_MySQL
  3. 019_with语句
  4. [原创]位运算符实现两个整数加法运算
  5. PCL:描述三维离散点的ROPS特征(Code)
  6. 虚拟dom添加虚拟dom_虚拟DOM缓慢。 认识记忆化的DOM
  7. php 获取cookieid,Redis实现Session共享详解
  8. 键盘发展简史:144年独孤求败的QWERT键盘
  9. git恢复到master版本_关于git,请教如何恢复版本库?
  10. 信息安全技术网络安全等级保护定级指南_报业网络安全等级保护定级参考指南V2.0发布...
  11. mysql查询表字段默认值
  12. 错误未找到引用源_你好,C++(77)12.1 用右值引用榨干C++的性能
  13. bzoj 3505: [Cqoi2014]数三角形 排列组合+数学
  14. list去重和list倒叙
  15. PS选区工具和羽化的运用
  16. 针对elementUI 中InfiniteScroll按需引入的一点注意事项
  17. HVF5220-4D-03,HVF3230-3G-02先导型二位五通电磁阀
  18. 2023计算机毕业设计SSM最新选题之java光明小区物业管理系统wjomh
  19. 推荐一款PDF转换软件-易奥PDF转换大师
  20. 高手日志终结篇:我所知道的那些马丁策略(下篇)

热门文章

  1. MyBatis 注解实现动态SQL
  2. 如何实现Android指纹登录
  3. Visual Studio 2019 许可证过期解决办法
  4. VGAME手游如何用电脑玩 VGAME手游PC电脑版教程
  5. 关于ActivityThread
  6. 吐槽Windows 8,就没见过这么烂的平板操作系统
  7. ensp企业网综合实验(课程设计)
  8. 财务RPA,财务RPA机器人的应用场景有哪些?
  9. linux中如何递归搜索文件,关于linux:递归查找具有特定扩展名的文件
  10. 应用实践 | 10 亿数据秒级关联,货拉拉基于 Apache Doris 的 OLAP 体系演进