委托(C#入门详解学习笔记)
委托(C#入门详解学习笔记)
- 几个概念
- 什么是委托
- 委托的声明(自定义委托)
- 委托的常规使用
- 通用泛型委托类型的简单使用(Func和Action)
- 委托的高级使用
-
- 多播委托
- 委托的异步调用
- 使用接口取代委托
几个概念
一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
直接调用与间接调用
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
- 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回
什么是委托
委托(delegate)可以理解为是C/C++中函数指针的”升级版“。
函数指针示例:
#include <stdio.h>//函数指针
typedef int (*Cal)(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 = 50;int z = 0;//将函数Add的地址给到函数指针function01Cal function01 = &Add;//将函数Sub的地址给到函数指针function02Cal function02 = ⋐//直接调用z = Add(x,y);printf("%d+%d=%d\\n",x,y,z);//间接调用z = function01(x,y);printf("%d+%d=%d\\n",x,y,z);//直接调用z = Sub(x,y);printf("%d-%d=%d",x,y,z);//间接调用z = function02(x,y);printf("%d-%d=%d",x,y,z);return 0;
}
委托的声明(自定义委托)
委托是一种类(class),类是数据类型,所以委托也是一种数据类型。类可以声明变量、创建实例,所以委托也可以。
委托的声明格式与C#中一般的类的声明格式不同,反而更像是C/C++中函数指针的声明格式。这样做主要是为了照顾可读性,并与C/C++传统保持一致。
注意点:
- 委托与所封装的方法必须”类型兼容“
- 委托声明与所封装的方法的返回值的数据类型一致
- 委托声明与所封装的方法的参数列表在个数和数据类型上一致
- 注意声明委托的位置
- 委托是一种类,声明时应该放在namespace中,使与其它类(class)于同一级别,避免放错位置结果声明成了嵌套类。
委托声明示例:
using System;namespace CSharp_DelegateLearningExample
{public delegate double Calc(double x, double y); class Program{static void Main(string[] args){Calculator calculator = new Calculator();Calc calcAdd = new Calc(calculator.Add);Calc calcSub = new Calc(calculator.Sub);Calc calcMul = new Calc(calculator.Mul);Calc calcDiv = new Calc(calculator.Div);double x = 100;double y = 50;double z = 0;z = calcAdd.Invoke(x, y);Console.WriteLine(z);z = calcSub.Invoke(x, y);Console.WriteLine(z);z = calcMul(x, y);Console.WriteLine(z);z = calcDiv(x, y);Console.WriteLine(z);}}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;}}
}
委托的常规使用
在日常工作当中,一般是使用委托封装方法,然后将委托作为参数,将委托封装的方法作为参数传递到另一个方法中使用。这样在另一个方法的方法体中就可以使用传进来的参数,间接地调用委托封装的那个方法,这样就形成了一种动态调用方法的代码结构。
而在具体使用过程中,可以分为两种方式:
-
模板方式:当前所写的方法,借用传递进来的委托参数所指定的外部方法来产生结果。
- 委托有返回值
- 常位于代码中部
- 代码逻辑相当于”填空题“:空白处用传进来的委托类型的参数进行填补,也就是用传递进来的委托参数间接地调用所指定的外部方法。
-
回调方式:把委托类型的参数传进主调方法里面去,被传进主调方法里的委托类型的参数,内部封装了一个被回调的方法,主调方法根据自己的逻辑决定是否调用该回调方法。回调方法一般用来执行一些后续工作。
-
委托无返回值
-
代码逻辑相当于”流水线“
-
常位于代码末尾
-
注意:委托功能强大,易使用,但难精通,一旦滥用则后果非常严重。
- 委托时方法级别的耦合,这种耦合往往违反设计模式,工作中要慎之又慎使用。
- 可读性下降,debug难度增加
- 委托回调、异步调用、多线程等纠缠在一起,则会非常难以维护
- 使用不当可能造成内存泄漏和程序性能下降
- 委托所引用的一个方法,如果这个方法是一个实例方法的话,那么这个方法必定隶属于一个对象。该对象必定存在于内存当中,且不能被释放。因为一旦释放,委托则无法间接调用该方法了。所以可能产生内存泄漏。
示例:
using System;namespace CSharp_DelegateLearning
{class Program{static void Main(string[] args){ProductFactory productFactory = new ProductFactory();WrapFactory wrapFactory = new WrapFactory();Func<Product> funcMakePizza = new Func<Product>(productFactory.MakePizza);Func<Product> funcMakeToyCar = new Func<Product>(productFactory.MakeToyCar);Logger logger = new Logger();Action<Product> actionLog = new Action<Product>(logger.Log);Box box1 = wrapFactory.WrapProduct(funcMakePizza, actionLog);Box box2 = wrapFactory.WrapProduct(funcMakeToyCar, actionLog);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}class Logger{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{/此处getProduct为模板方式,logCallback为回调方式* * 模板方式* 好处:该模板方式可以多次复用。* 使用此方式后,当需要增加产品时,* Product、Box、WrapFactory类均可以不改变,在ProductFactory中增加新产品的方法即可* 之后只需要将新产品的生产方法封装到一个委托中,就可以通过模板方法完成产品包装,* 从而最大限度实现代码重复使用* * 回调方式* 好处:主调方法根据自己的逻辑决定是否调用该回调方法*/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 pizza = new Product();pizza.Name = "Pizza";pizza.Price = 12;return pizza;}public Product MakeToyCar(){Product toyCar = new Product();toyCar.Name = "Toy Car";toyCar.Price = 100;return toyCar;}}
}
通用泛型委托类型的简单使用(Func和Action)
在System命名空间下定义了Func和Action两个委托,它们使用了泛型类型参数。
Func设置必须要有一个返回值参数,所以常用于对有返回值的函数设置委托;而Action委托则没有返回值参数。
Func和Action委托均可以使用0到16个输入参数。
Func和Action委托使用示例:
using System;namespace CSharp_DelegateLearning_FuncAction
{class Program{static void Main(string[] args){Calculator calculator = new Calculator();Action action = new Action(calculator.Report);calculator.Report(); //直接调用action.Invoke(); //间接调用action(); //action.Invoke()的简便写法Func<int, int, int> funcAdd = new Func<int, int, int>(calculator.Add);Func<int, int, int> funcSub = new Func<int, int, int>(calculator.Sub);int x = 100;int y = 40;int z = 0;z = funcAdd.Invoke(x, y);z = funcAdd(x, y);Console.WriteLine(z);z = funcSub.Invoke(x, y);z = funcSub(x, y);Console.WriteLine(z);}}class Calculator{public void Report(){Console.WriteLine("I have three functions.");}public int Add(int x, int y){return x + y;}public int Sub(int x, int y){return x - y;}}
}
委托的高级使用
多播委托
多播委托,即一个委托内封装的不止一个方法。委托可以使用 + 和 += 运算符联结多个委托实例;可以用 - 和 -= 运算符从左侧委托操作数中将右侧委托操作数删除。
多播委托中方法的执行顺序,是封装方法的顺序。示例:
using System;
using System.Threading;namespace CSharp_DelegateLearning_MulticastDelegate
{class Program{static void Main(string[] args){Student stu1 = new Student() { Name = "zhao", PenColor = ConsoleColor.Green };Student stu2 = new Student() { Name = "qian", PenColor = ConsoleColor.Yellow };Student stu3 = new Student() { Name = "sun", PenColor = ConsoleColor.Red };Action action1 = new Action(stu1.DoHomework);Action action2 = new Action(stu2.DoHomework);Action action3 = new Action(stu3.DoHomework);action1 += action2;action1 += action3;action1.Invoke();}}class Student{public string Name { 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", this.Name, i);Thread.Sleep(1000);}}}
}
委托的异步调用
同步调用:
- 两段程序,一段在另一段执行完成的基础上再执行;
- 同步调用是在同一线程内进行,即在单线程中串行调用。
异步调用:
- 两段程序,同时执行,二者之间会相互抢占资源;
- 异步调用是基于多线程的,即在多线程中并行调用。
在异步调用时,又有隐式多线程和显式多线程的区分:
- 隐式异步调用:使用委托的 BeginInvoke();
- 该方法会自动生成一个分支线程,然后在分支线程中调用封装的方法。
- 该方法在 .net core 中不支持
- 显式异步调用:使用Thread或Task,Thread是比较老的写法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace CSharp_DelegateLearning_AsyncCall
{class Program{static void Main(string[] args){Student stu1 = new Student() { Name = "zhao", PenColor = ConsoleColor.Green };Student stu2 = new Student() { Name = "qian", PenColor = ConsoleColor.Yellow };Student stu3 = new Student() { Name = "sun", PenColor = ConsoleColor.Red };/隐式异步调用:该方法在 .net core 中不支持*此时三个线程会因为资源发生冲突,导致字体颜色多次变化*解决这些冲突通常需要给线程加锁。*///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);//显示异步调用://方法1:使用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();*///方法2: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.White;Console.WriteLine("Main Thread {0}", i);Thread.Sleep(1000);}}}class Student{public string Name { 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", this.Name, i);Thread.Sleep(1000);}}}
}
使用接口取代委托
在对委托使用不当的时候,会降低代码的可读性,不利于维护。而使用接口则可以避免这些麻烦,且同样可以获得相应的功能。Java中就完全用接口取代了委托的功能。
using System;namespace CSharp_DelegateLearning_InterfaceReplaceDelegate
{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 MakeProduct();}class PizzaFactory : IProductFactory{public Product MakeProduct(){Product pizza = new Product();pizza.Name = "Pizza";return pizza;}}class ToyCarFactory : IProductFactory{public Product MakeProduct(){Product toyCar = new Product();toyCar.Name = "Toy Car";return toyCar;}}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.MakeProduct();box.Product = product;return box;}}
}