委托
委托(delegate)是函数指针的升级版
委托是一种类(Class),类是数据类型,所以委托也是一种数据类型
static void Main(string[] args)
{
Type t = typeof(Action);
Console.WriteLine(t.IsClass);//输出True,则委托为类(Class)
}
直接调用与间接调用
- 直接调用:使用函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行
- 间接调用:使用函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行
直接调用和间接调用效果一样
C语言非函数指针直接调用:
#include <stdio.h>
int Add(int x, int y)//加法函数
{
return x + y;
}
int Sub(int x, int y)//减法函数
{
return x - y;
}
int main()
{
int x = 100;
int y = 200;
int z;
z = Add(x, y);//输入函数名直接调用
printf("%d + %d = %d\n", x, y, z);
z = Sub(x, y);
printf("%d - %d = %d\n", x, y, z);
return 0;
}
C语言使用函数指针间接调用:
#include <stdio.h>
typedef int(*Cal)(int x, int y);//获得函数指针类型
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int x = 100;
int y = 200;
int z;
Cal funcPoint1 = &Add;//声明函数指针类型的变量
Cal funcPoint2 = ⋐
z = funcPoint1(x, y);
printf("%d + %d = %d\n", x, y, z);
z = funcPoint2(x, y);
printf("%d - %d = %d\n", x, y, z);
return 0;
}
一切皆为地址
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
Action委托例子
Action委托只能对返回值为空的方法进行委托
internal class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report);//Action(void() target)
//Action创建实例,后面圆括号内只能接一个返回值为空的方法
calculator.Report();//直接调用
action.Invoke();//间接调用
action();//简写
}
}
class Calculator
{
public void Report()
{
Console.WriteLine("I'm Calculator.");
}
public int Add(int a, int b)
{
return a + b;
}
public int Sub(int a, in int b)
{
return a - b;
}
}
Function委托例子
Function委托有17种类型重载
代码中写为:Func
internal class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
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'm Calculator.");
}
public int Add(int a, int b)
{
return a + b;
}
public int Sub(int a, int b)
{
return a - b;
}
}
C#自带的委托
即为 Action 和 Function 这两种委托,可以直接用
委托的声明
即为自定义委托
虽然委托是类,但委托声明方式与一般的类不同,声明更像是C/C++中的函数指针的声明,主要是为了增加可读性和继承C/C++的传统
public delegate double Cal(double x, double y);
//第一个double - 目标方法的返回类型
//圆括号内 - 目标方法的参数列表
internal class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Cal cal1 = new Cal(calculator.Add);
Cal cal2 = new Cal(calculator.Sub);
Cal cal3 = new Cal(calculator.Mul);
Cal cal4 = new Cal(calculator.Div);
double x = 100;
double y = 200;
double z;
z = cal1.Invoke(x, y);//".Invoke"可以删去
Console.WriteLine(z);
z = cal2.Invoke(x, y);
Console.WriteLine(z);
z = cal3.Invoke(x, y);
Console.WriteLine(z);
z = cal4.Invoke(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;
}
}
注意:委托与所封装的方法必须“类型兼容”
delegate double Cal(double x, double y);
------------double Add(double x, double y) { return x + y; }
------------double Sub(double x, double y) { return x - y; }
------------double Mul(double x, double y) { return x * y; }
------------double Div(double x, double y) { return x / y; }
声明委托时不要放错位置,要声明在名称空间体中,以免被C#编译器认为是嵌套类(在类中声明一个类)
委托的一般使用
委托一般当作方法的参数传到另一个方法中去
- 模板方法:借用指定的外部方法来产生结果
- 可以增加代码重复使用率
internal class Program
{
static void Main(string[] args)
{
ProjectFactory projectFactory = new ProjectFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Project> func1 = new Func<Project>(projectFactory.MakeGame);
Func<Project> func2 = new Func<Project>(projectFactory.MakeMovie);
Box box1 = wrapFactory.WrapProject(func1);
Box box2 = wrapFactory.WrapProject(func2);
Console.WriteLine(box1.Project.Name);
Console.WriteLine(box2.Project.Name);
}
}
class Project//项目
{
public string Name { get; set; }
}
class Box//项目封装箱
{
public Project Project { get; set; }
}
class WrapFactory//封装工厂
{
public Box WrapProject(Func<Project> getProject)
{ //模板方法
Box box = new Box();//创建一个封装箱
Project project = getProject.Invoke();//获取一个项目
box.Project = project;//将项目放进封装箱中
return box;//返还封装箱
}
}
class ProjectFactory//项目工厂
{
public Project MakeGame()
{
Project project = new Project();
project.Name = "Game";
return project;
}
public Project MakeMovie()
{
Project project = new Project();
project.Name = "Movie";
return project;
}
}
- 回调(Callback)方法:调用指定的外部方法。一般位于主调方法的末尾,且一般没有返回值
//项目价格高于100时,打印生产项目时的标准时间
internal class Program
{
static void Main(string[] args)
{
ProjectFactory projectFactory = new ProjectFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Project> func1 = new Func<Project>(projectFactory.MakeGame);
Func<Project> func2 = new Func<Project>(projectFactory.MakeMovie);
Logger logger = new Logger();
Action<Project> log = new Action<Project>(logger.Log);
Box box1 = wrapFactory.WrapProject(func1, log);
Box box2 = wrapFactory.WrapProject(func2, log);
Console.WriteLine(box1.Project.Name);
Console.WriteLine(box2.Project.Name);
}
}
class Logger//记录程序运行状态
{
public void Log(Project project)
{
Console.WriteLine("Project \"{0}\" created at {1}, Price is {2}", project.Name, DateTime.UtcNow, project.Price);
//DateTime.UtcNow - 不带时区的时间
}
}
class Project//项目
{
public string Name { get; set; }
public double Price { get; set; }//项目价格
}
class Box//项目封装箱
{
public Project Project { get; set; }
}
class WrapFactory//封装工厂
{
public Box WrapProject(Func<Project> getProject, Action<Project> logCallback)
{ //开始回调
Box box = new Box();//创建一个封装箱
Project project = getProject.Invoke();//获取一个项目
if (project.Price > 100)
{
logCallback(project);
}
box.Project = project;//将项目放进封装箱中
return box;
}
}
class ProjectFactory//项目工厂
{
public Project MakeGame()
{
Project project = new Project();
project.Name = "Game";
project.Price = 298;
return project;
}
public Project MakeMovie()
{
Project project = new Project();
project.Name = "Movie";
project.Price = 50;
return project;
}
}
注意:委托难精通、易使用、功能强大
- 这是一种方法级别的紧耦合
- 容易使可读性下降,增加debug难度
- 把委托回调、异步调用、多线程纠缠在一起时,会让代码变得很难维护
- 委托使用不当可能造成内存泄漏和程序性能下降
委托的高级使用
多播(Multicast)委托
即一个委托内封装着多个委托
- 单播委托
- 一个委托封装一个方法
//using System.Threading;
internal class Program
{
static void Main(string[] args)
{
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action a1 = new Action(s1.DoHomework);
Action a2 = new Action(s2.DoHomework);
Action a3 = new Action(s3.DoHomework);
a1.Invoke();
a2.Invoke();
a3.Invoke();
}
}
class Student
{
public int ID { get; set; }//学生ID
public ConsoleColor PenColor { get; set; }//学生使用的笔的颜色
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
if (i == 0)
{
Console.WriteLine("Student {0} starts doing homework.", this.ID);
continue;
}
Thread.Sleep(1000);//1000ms = 1s
//在哪个线程中调用了"Thread.Sleep",此线程就暂停 1s
Console.WriteLine("Student {0} has done homework {1} hour(s).", this.ID, i);
}
}
}
- 多播委托
//将单播委托例子中的Main方法修改为如下
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action a1 = new Action(s1.DoHomework);
Action a2 = new Action(s2.DoHomework);
Action a3 = new Action(s3.DoHomework);
a1 += a2;//将a2封装进a1
a1 += a3;//将a3封装进a1
a1.Invoke();//执行顺序为封装的先后顺序
异步调用
同步调用:一个人做完后,下一个人在前一个人做完的基础上接着做
异步调用:两个人同时做
同步调用与异步调用:每一个运行的程序是一个进程(Process),一个进程可以有一个或者多个线程(Thread)。同步调用是在一个线程内进行,异步调用是在多个线程中进行
同步调用是在单线程中进行串行调用
异步调用是在多线程中进行并行调用
可以利用委托进行隐式异步调用
- 同步调用
//修改上段代码中的Main方法
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
s1.DoHomework();
s2.DoHomework();
s3.DoHomework();
//一个一个调用
//使用委托进行间接同步调用
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action a1 = new Action(s1.DoHomework);
Action a2 = new Action(s2.DoHomework);
Action a3 = new Action(s3.DoHomework);
a1.Invoke();
a2.Invoke();
a3.Invoke();
//单播、多播委托都能同步调用
//a1 += a2;
//a1 += a3;
//a1.Invoke();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Thread.Sleep(1000);
Console.WriteLine("Main Thread {0}.", i);
}
- 隐式异步调用
- 分支线程是由BeginInvoke自动生成的
//修改上面的Main方法
//使用委托进行隐式异步调用
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action a1 = new Action(s1.DoHomework);
Action a2 = new Action(s2.DoHomework);
Action a3 = new Action(s3.DoHomework);
a1.BeginInvoke(null, null);//BeginInvoke会自动生成一个分支线程,再在分支线程中调用方法
//需要两个参数
//1. 异步调用的回调 - 调用完方法后后续的行动
//2. 向回调函数中传递参数
//不使用参数则写做 null
a2.BeginInvoke(null, null);
a3.BeginInvoke(null, null);
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Thread.Sleep(1000);
Console.WriteLine("Main Thread {0}.", i);
}
- 显示异步调用
- 手动生成线程
//使用Thread
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Thread t1 = new Thread(new ThreadStart(s1.DoHomework));
Thread t2 = new Thread(new ThreadStart(s2.DoHomework));
Thread t3 = new Thread(new ThreadStart(s3.DoHomework));
t1.Start();
t2.Start();
t3.Start();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Thread.Sleep(1000);
Console.WriteLine("Main Thread {0}.", i);
}
//使用Task
Student s1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student s2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student s3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Task t1 = new Task(new Action(s1.DoHomework));
Task t2 = new Task(new Action(s2.DoHomework));
Task t3 = new Task(new Action(s3.DoHomework));
t1.Start();
t2.Start();
t3.Start();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Thread.Sleep(1000);
Console.WriteLine("Main Thread {0}.", i);
}
使用接口取代委托
应适当使用接口(interface)取代一些对委托的使用,可以避免一些不必要的麻烦还可以有相同的效果
internal class Program
{
static void Main(string[] args)
{
IProjectFactory gameFactory = new GameFactory();
IProjectFactory movieFactory = new MovieFactory();
WrapFactory wrapFactory = new WrapFactory();
Box box1 = wrapFactory.WrapProject(gameFactory);
Box box2 = wrapFactory.WrapProject(movieFactory);
Console.WriteLine(box1.Project.Name);
Console.WriteLine(box2.Project.Name);
}
}
interface IProjectFactory
{ //接口
Project Make();
}
class GameFactory : IProjectFactory
{
public Project Make()
{
Project project = new Project();
project.Name = "Game";
return project;
}
}
class MovieFactory : IProjectFactory
{
Project IProjectFactory.Make()
{
Project project = new Project();
project.Name = "Movie";
return project;
}
}
class Project//项目
{
public string Name { get; set; }
public double Price { get; set; }//项目价格
}
class Box//项目封装箱
{
public Project Project { get; set; }
}
class WrapFactory//封装工厂
{
public Box WrapProject(IProjectFactory projectFactory)
{
Box box = new Box();
Project project = projectFactory.Make();
box.Project = project;
return box;
}
}