引用:https://www.bilibili.com/video/BV1wx411K7rb?p=19
1.委托就是c语言中的函数指针(指向函数的指针)的升级版,可以按照一定的约束,将对方法的引用作为实参传给另一个方法。
2.程序员常说一切皆地址,程序的本质就是数据加算法,数据是存储在变量中的,本质是地址,函数代表了算法,本质也是地址。
变量(数据)是以某个内存地址为起点的一段内存中存储的值。至于数据多大,取决于存储地址的类型决定的。
函数(算法)是以某个内存地址为起点的一段内存中所存储的机器语言指令。
CPU就是按照这些机器语言指令来执行,完成算法。
变量是用来寻找数据的地址,函数是用来寻找算法的地址,这就是常说的一切皆地址。
3.直接调用和间接调用
(1)直接调用:通过函数名来调用函数。CPU通过函数名直接获得函数所在地址并执行函数。
(2)间接调用:通过函数指针调用函数,函数指针作为一个变量,里面存储的就是这个函数的地址,CPU通过获取函数指针存储的值获得函数所在地址并开始执行。
4.委托就是一个对象,它知道如何调用一个方法。
(1)委托类型:定义了委托实例可以调用的那类方法,具体说就是委托类型定义了方法的返回类型和参数
比如下面:
这是一个委托类型,定义了这个委托可以调用返回类型为int,参数为int的方法,
delegate int Transformer(int x);
比如下面2个:
static int Squre(int x) { return x * x; } static int Squre(int x) => x * x;
委托实例:把方法赋给委托变量的时候就创建了委托实例,比如下面:
Transformer t=Squre;
调用:
int value = t(3);
下面是一个简单例子:解耦(降低耦合度)
class Program { delegate int Transformer(int x); static int Squre(int x) { return x * x; } static void Main(string[] args) { //创建委托实例 Transformer t = Squre; int result = t(3);//委托实例来调用目标方法,这样会实现解耦 Console.WriteLine(result); Console.ReadKey(); } }
Func<Result>,Func<T1,Result>是一个.Net内置的泛型委托。
- Func<TResult>
- Func<T,TResult>
- Func<T1,T2,TResult>
- Func<T1,T2,T3,TResult>
- Func<T1,T2,T3,T4,TResult>
它有5种形式,只是参数个数不同;第一个是无参数,但是有返回值
Action<T>的用法与Func几乎一样,调用方法也类似,但是没有返回值。
- Action
- Action<T>
- Action<T1,T2>
- Action<T1,T2,T3>
- Action<T1,T2,T3,T4>
下面是一个简单的例子:
namespace TestClass { public delegate double Calc(int a, int b); class Program { public delegate double Calc(int a,int b); static void Main(string[] args) { //调用Report方法 Calculator calculator = new Calculator(); //calculator.Report:把Calculator类中的Report对象名称给了action, //CPU通过获取函数指针存储的值获得函数所在地址并开始执行。 Action action = new Action(calculator.Report); //执行,下面2种方式都可以。 action.Invoke(); action(); //Func Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add); Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub); func1.Invoke(1,2); func1(1,2); func1.Invoke(5,6); func1(5,6); //自定义委托 Calc cal = new Calc(calculator.Test); cal(7,8); Console.WriteLine(cal(7, 8)); Console.ReadKey(); } } public class Calculator { public void Report() { Console.WriteLine("Report"); } public int Add(int a,int b) { return a + b; } public int Sub(int a, int b) { return a - b; } public double Test(int a, int b) { return a - b; } } }
委托也是一种类,所以也是数据类型,比如下面:
Type type = typeof(Action); Console.WriteLine(type.IsClass);
结果:True
5.委托的一般使用
委托将方法作为参数传递给另一个方法又细分为2种:
1.模板方法
比如写了一个方法,然后通过传进来的委托参数,"借用"指定的外部方法来产生一个结果。常位于代码中部,委托有返回值。
2.回调方法
调用指定的外部方法,常位于代码的末尾,委托无返回值。比如一个人给了你一张名片,有事情的时候可以找他帮忙,没有事情的话就不用找他帮忙。此时就构成了回调关系,就是可以调用,也可以不调用。而且回调方法可以让我们动态的调用选择回调的方法,就比如你从一堆名片中选出一个寻求帮助。回调方法一般都是执行后续的工作。
还有一个例子,就是一个人在面试,面试官告诉他如果通过了就会打电话通知,此人不需要打电话问。
下面是模板方法的实例:
namespace TestClass { 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.MakeToCar); Box box1 = wrapFactory.WrapProduct(func1); Box box2 = wrapFactory.WrapProduct(func2); Console.WriteLine(box1.Product.Name); Console.WriteLine(box2.Product.Name); Console.ReadKey(); } } /// <summary> /// 产品类 /// </summary> public class Product { public string Name { get; set; } } /// <summary> /// 包装箱 /// </summary> public class Box { /// <summary> /// 里面含有的产品 /// </summary> public Product Product { get; set; } } /// <summary> /// 专门包装产品的工厂 /// </summary> public class WrapFactory { /// <summary> /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。 /// 这里接收的是Product类参数, /// </summary> /// <param name="getProduct"></param> /// <returns></returns> public Box WrapProduct(Func<Product> getProduct) { Box box = new Box(); //调用对应的产品方法, 也可以写成getProduct.Invoke() Product product = getProduct(); box.Product = product; return box; } } /// <summary> /// 专门生产产品的工厂 /// </summary> public class ProductFactory { /// <summary> /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。 /// 这里接收的是Product类参数, /// </summary> /// <param name="getProduct"></param> /// <returns></returns> public Product MakePizza() { Product product =new Product(); product.Name = "Pizza"; return product; } public Product MakeToCar() { Product product = new Product(); product.Name = "Car"; return product; } } }
结果:
Pizza
Car
使用模板方法的好处:从上面代码我们可以看出,此时Box类,WrapFactory类,都不用再动,只需要不停的扩展产品工厂。不管我们生产什么产品,只要封装在一个委托对象中,传给模板方法,就可以包装成一个箱子返回。
回调方法示例:
namespace TestClass { 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.MakeToCar); Logger logger = new Logger(); Action<Product> action = new Action<Product>(logger.Log); Box box1 = wrapFactory.WrapProduct(func1, action); Box box2 = wrapFactory.WrapProduct(func2, action); Console.WriteLine(box1.Product.Name); Console.WriteLine(box2.Product.Name); Console.ReadKey(); } } /// <summary> /// 产品类 /// </summary> public class Product { public string Name { get; set; } public double Price { get; set; } } /// <summary> /// 日志 /// </summary> public class Logger { public void Log(Product product ) { Console.WriteLine("时间"+DateTime.UtcNow); } } /// <summary> /// 包装箱 /// </summary> public class Box { /// <summary> /// 里面含有的产品 /// </summary> public Product Product { get; set; } } /// <summary> /// 专门包装产品的工厂 /// </summary> public class WrapFactory { /// <summary> /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。 /// 这里接收的是Product类参数, /// </summary> /// <param name="getProduct"></param> /// <returns></returns> public Box WrapProduct(Func<Product> getProduct,Action<Product> action) { Box box = new Box(); //调用对应的产品方法, 也可以写成getProduct.Invoke() Product product = getProduct(); if(product.Price>=50) { //当做回调函数用。 action(product); } box.Product = product; return box; } } /// <summary> /// 专门生产产品的工厂 /// </summary> public class ProductFactory { /// <summary> /// 这是一个模板方法,接收产品,进行包装,并返回包装完的产品。 /// 这里接收的是Product类参数, /// </summary> /// <param name="getProduct"></param> /// <returns></returns> public Product MakePizza() { Product product =new Product(); product.Name = "Pizza"; product.Price = 12; return product; } public Product MakeToCar() { Product product = new Product(); product.Name = "Car"; product.Price = 102; return product; } } }
结果:
时间2020/6/21 5:39:00
Pizza
Car
注意,委托使用不当会造成下面的问题:
1.这是一种方法级别的紧耦合,现实工作中要慎用。
2.可读性下降,debug难度增加。
3.把委托调用,异步调用,多线程纠缠在一起,会让代码变得难以阅读和维护。
4.使用不当会造成性能下降和内存泄漏。因为委托会引用一个方法,这个方法是一个实例方法的话,那这个方法是一定属于一个对象,拿委托引用这个方法,这个对象就必须存在内存当中,就算是没有其他的引用类性引用这个对象,这个对象的内存也不能释放,因为一旦释放,委托就不能间接调用对象的方法了。所以可能会造成内存泄漏,并引起性能下降。
6.多播委托
包含多个方法的委托成为多播委托,调用多播委托,可以按照顺序连续调用多个方法,因此,委托的签名就必须返回void;
例子如下:
namespace TestClass { class Program { static void Main(string[] args) { Student student1 = new Student() {ID=1,PenColor=ConsoleColor.Black }; Student student2 = new Student() { ID = 2, PenColor = ConsoleColor.Green }; Student student3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow }; Action action = new Action(student1.DoWork); Action action2 = new Action(student1.DoWork); Action action3 = new Action(student1.DoWork); //将action2,action3赋给了action,这样就可以只执行action的方法就可以把所有委托实现 action += action2; action += action3; action.Invoke(); Console.ReadKey(); } } public class Student { public int ID { get; set; } public ConsoleColor PenColor { get; set; } public void DoWork() { for(int i=0;i<5;i++) { Console.ForegroundColor = this.PenColor; Console.WriteLine($"Student {ID} doing homework {i} hours"); Thread.Sleep(1000); } } } }
7.隐式异步调用
同步指的是从上向下顺序调用。
隐式的异步调用BeginInvoke方法即可;BeginInvoke(null,null);第一个参数是回调函数,后面是参数值。
8.显式异步调用
static void Main(string[] args) { Student student1 = new Student() { ID = 1, PenColor = ConsoleColor.Black }; Student student2 = new Student() { ID = 2, PenColor = ConsoleColor.Green }; Student student3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow }; Task task1 = new Task(new Action(student1.DoWork)); Task task2 = new Task(new Action(student2.DoWork)); Task task3 = new Task(new Action(student3.DoWork)); task1.Start(); task2.Start(); task3.Start(); for (int i = 0; i < 5; i++) { Console.WriteLine($"第{i}次"); Thread.Sleep(1000); } Console.ReadKey(); }