委托(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 = ⋐
//通过函数指针来调用函数
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+b
a = 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;
}
}
不再有方法级别的耦合。