c#之委托
引用: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);
//也可以按照下面形式调用
Box box1 = wrapFactory.WrapProduct(productFactory.MakePizza);
Box box2 = wrapFactory.WrapProduct(productFactory.MakeToCar);
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.使用不当会造成性能下降和内存泄漏。因为委托会引用一个方法,这个方法是一个实例方法的话,那这个方法是一定属于一个对象,拿委托引用这个方法,这个对象就必须存在内存当中,就算是没有其他的引用类性引用这个对象,这个对象的内存也不能释放,因为一旦释放,委托就不能间接调用对象的方法了。所以可能会造成内存泄漏,并引起性能下降。
内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
内存溢出:指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
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); } } } }
还可以实现下面的调用,可以传参数:
class Program { static void Main(string[] args) { Student student = new Student(); student.Show(); Console.ReadKey(); } } public class Student { public Action action; public void Show() { 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 += new Student().DoWork; //可以传参数 action += () => new Student().DoWork1("s"); action.Invoke(); } 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"); } } public void DoWork1(string name) { for (int i = 0; i < 5; i++) { Console.ForegroundColor = this.PenColor; Console.WriteLine($"Student {ID} doing homework {i} hours"); } } }
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(); }
最后,为什么要使用委托的一个例子:https://www.cnblogs.com/xiaoyehack/p/9647526.html
9.委托的扩展:俄罗斯套娃(类似于asp.netcore中的管道模型原理)
如上图,方法一层一层的递进,最后一层一层的出来。
public class DelegateExtend { /// <summary> /// 俄罗斯套娃内部原理 /// </summary> public static void Show() { //定义一个委托,参数和返回值都是委托 Func<Action, Action> func1 = new Func<Action, Action>(act => { return new Action(() => { Console.WriteLine("func1 start"); act?.Invoke(); Console.WriteLine("func1 end"); }); }); Func<Action, Action> func2 = new Func<Action, Action>(act => { return new Action(() => { Console.WriteLine("func2 start"); act?.Invoke(); Console.WriteLine("func2 end"); }); }); Func<Action, Action> func3 = new Func<Action, Action>(act => { return new Action(() => { Console.WriteLine("func3 start"); act?.Invoke(); Console.WriteLine("func3 end"); }); }); List<Func<Action, Action>> list = new List<Func<Action, Action>>() { func1, func2, func3 }; list.Reverse();//反转 Action action = null; foreach (var act in list) { action = act.Invoke(action); } action.Invoke(); } }
结果:
func1 start
func2 start
func3 start
func3 end
func2 end
func1 end
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术
2019-06-09 c#基础之Dictionary
2019-06-09 数据结构之权值(在吊挂中的实际应用)
2019-06-09 c#基础之ADO.NET(二)