几个概念
一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所存储的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
直接调用与间接调用
- 直接调用:通过函数名来调用函数,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的地址给到函数指针function01
Cal function01 = &Add;
//将函数Sub的地址给到函数指针function02
Cal 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;
}
}
}
转载:https://blog.csdn.net/SQWH_SSGS/article/details/129171905