何为委托
//定义一个函数指针数据类型 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 funcPoint1 = &Add;
Calc funcPoint2 = ⋐
//通过函数指针间接调用
zz = funcPoint1(x, y);
printf("%d+%d=%d\n",x,y,z);
z = funcPoint2(x, y);
printf("%d+%d=%d\n",x,y,z);
system("pause");
return 0;
}
- 一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
- 直接调用与间接调用
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
- 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回
- Java中没有与委托相对应的功能实体
- 委托的简单使用
- Action委托:调用没有返回值的函数
- Func委托:调用有返回值的函数
```csharp
using System;
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
Calculator cal = new Calculator();
Action action = new Action(cal.Report);//创建Action委托
cal.Report();//函数的直接调用
action.Invoke();//间接调用
action();//间接调用的简易写法
Func<int, int, int> funcAdd = new Func<int, int, int>(cal.Add);//创建Func委托
Func<int, int, int> funcSub = new Func<int, int, int>(cal.Sub);
int x = 100, y = 200, z = 0;
z = funcAdd.Invoke(x,y);
Console.WriteLine(z);
z = funcSub(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;
}
}
}
委托的声明(自定义委托)
- 委托是一种类(class),类是数据类型所以委托也是一种数据类型
- 它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
- 注意声明委托的位置
- 避免写错地方结果声明成嵌套类型
- 委托与所封装的方法必须”类型兼容”
- 声明格式如下:
- 示例: ```csharp using System;
namespace DelegateExample { //声明自定义委托 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, b = 200, 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 a, double b)
{
return a + b;
}
public double Sub(double a, double b)
{
return a - b;
}
public double Mul(double a, double b)
{
return a * b;
}
public double Div(double a, double b)
{
return a / b;
}
}
}
<a name="hNXTb"></a>
# 委托的一般使用
- 委托的一般使用:把方法当做参数传给另一个方法
<a name="qSIkD"></a>
## 模板方法
- 正确使用1:模板方法,"借用"指定的外部方法来产生结果
- 相当于"填空题"
- 常位于代码中部
- 委托有返回值
- 示例:模板方法的好处是,一旦写好后,Product类,Box类和WrapFactory类都无需改动,只需改变ProductFactory类,通过增加多个产品方法,最大限度地实现代码的复用。
代码的Reuse复用,不但可以提高工作效率,还可以减少bug的引入,良好的复用结构是所有优秀软件所追求的共同目标之一。
```csharp
using System;
namespace DelegateExample
{
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
//创建Func委托,返回值类型为Product,参数为空
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);//输出:Pizza
Console.WriteLine(box2.Product.Name);//输出:ToyCar
}
}
class Product
{
public string Name { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
//WrapProduct方法是一个模板方法,它的参数是委托类
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 = "ToyCar";
return product;
}
}
}
回调方法(callback)
- 正确使用2:回调(callback)方法,调用指定的外部方法
- 相当于”流水线”
- 常位于代码尾部
- 委托无返回值
- 示例:日常工作中对委托的常规用法 ```csharp using System;
namespace DelegateExample { class Program { static void Main(string[] args) { ProductFactory productFactory = new ProductFactory(); WrapFactory wrapFactory = new WrapFactory();
//创建Func委托,返回值类型为Product,参数为空
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Logger logger = new Logger();
//创建Action委托,参数类型为Product,无返回值
Action<Product> log = new Action<Product>(logger.Log);
//调用模板方法
Box box1 = wrapFactory.WrapProduct(func1, log);//输出:Product ToyCar created at 2021/6/12 8:48:49.Price is 100
Box box2 = wrapFactory.WrapProduct(func2, log);
Console.WriteLine(box1.Product.Name);//输出:Pizza
Console.WriteLine(box2.Product.Name);//输出:ToyCar
}
}
class Logger
{
public void Log(Product product)
{
Console.WriteLine($"Product {product.Name} created at {DateTime.UtcNow}.Price is {product.Price}");
}
}
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
//WrapProduct方法是一个模板方法和回调方法,它的参数是委托类
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 = "ToyCar";
product.Price = 100;
return product;
}
}
}
- 注意:难精通+易使用+功能强大的东西,一旦被滥用则后果非常严重
- 缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
- 缺点2:使可读性下降,debug的难度增加
- 缺点3:把委托回调,异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
- 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降
<a name="NXrTE"></a>
# 委托的高级使用
<a name="TguUP"></a>
## 多播委托
- 定义:用一个委托封装多个方法的使用方式
- 示例:
```csharp
using System;
using System.Threading;
namespace MulticastDelegateExample
{
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();
//以下为多播委托:将action2和action3都封装到action1中
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 {this.ID} doing homework {i} hours");
Thread.Sleep(1000);
}
}
}
}
- 输出结果为:
隐式异步调用
- 同步与异步的简介
- 中英文的语言差异
- 同步:你做完了我(在你的基础上)接着做
- 异步:咱们两个同时做(相当于汉语中的”同步进行”)
- 同步调用与异步调用的对比
- 每一个运行的程序是一个进程(process)
- 每个进程可以有一个或者多个线程(thread)
- 同步调用是在同一线程内
- 异步调用的底层机理是多线程
- 串行==同步==单线程,并行==异步==多线程
- 隐式多线程V.S.显式多线程
直接同步调用:使用方法名
- 示例: ```csharp using System; using System.Threading;
namespace MulticastDelegateExample { 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 };
//直接同步调用
stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {i}.");
Thread.Sleep(1000);
}
}
}
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 {this.ID} doing homework {i} hours");
Thread.Sleep(1000);
}
}
}
}
- 输出结果:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623835448134-73751533-d19d-4b4e-9de9-306be1bfc150.png#clientId=u4f3645af-6211-4&from=paste&height=288&id=u1bcaac99&margin=%5Bobject%20Object%5D&name=image.png&originHeight=576&originWidth=378&originalType=binary&ratio=2&size=41998&status=done&style=none&taskId=u409f496e-cf77-4322-b553-4e4f5865596&width=189)
<a name="VUtDw"></a>
### 间接同步调用:使用单播/多播委托的Invoke方法
<a name="Zg9Hy"></a>
### 隐式异步调用:使用委托的BeginInvoke
- 示例:
```csharp
using System;
using System.Threading;
namespace MulticastDelegateExample
{
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.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 {i}.");
Thread.Sleep(1000);
}
}
}
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 {this.ID} doing homework {i} hours");
Thread.Sleep(1000);
}
}
}
}
- 输出结果:
- 分析结果发现:Main线程、action1和action3线程都是红色,颜色出错。是因为这3个线程都在访问Console的ForegroundColor属性,当多个线程共享(即同时访问)同一个资源(比如某个变量)的情况,这时如果处理不当就会产生线程间争夺资源的冲突(可用线程锁lock解决)
显式异步调用:使用Thread或Task
- 示例:使用thread ```csharp using System; using System.Threading;
namespace MulticastDelegateExample { 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 };
Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
//显式异步调用thread
thread1.Start();
thread2.Start();
thread3.Start();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {i}.");
Thread.Sleep(1000);
}
}
}
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 {this.ID} doing homework {i} hours");
Thread.Sleep(1000);
}
}
}
}
- 输出结果:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21507654/1623836223188-935ef816-8c2f-4c5b-b999-cc73df44915b.png#clientId=u4f3645af-6211-4&from=paste&height=290&id=ufe5c9d00&margin=%5Bobject%20Object%5D&name=image.png&originHeight=579&originWidth=362&originalType=binary&ratio=2&size=48204&status=done&style=none&taskId=u1da08116-f0d5-445b-8792-dc832320159&width=181)
- 示例:使用task(更为高级),结果一致
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MulticastDelegateExample
{
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 };
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));
//显式异步调用task
task1.Start();
task2.Start();
task3.Start();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {i}.");
Thread.Sleep(1000);
}
}
}
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 {this.ID} doing homework {i} hours");
Thread.Sleep(1000);
}
}
}
}
应该适时地使用接口(interface)取代一些对委托的使用
- Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体
- 示例:使用接口重构前面的模板方法案例: ```csharp using System;
namespace DelegateExample { 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);//输出:Pizza
Console.WriteLine(box2.Product.Name);//输出:ToyCar
}
}
interface IProductFactory
{
Product Make();
}
class PizzaFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
}
class ToyCarFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "ToyCar";
return product;
}
}
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.Make();
box.Product = product;
return box;
}
}
} ```