C#学习 - 委托

委托

委托(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 = &Sub;
	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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值