什么是委托
- 委托(delegate)是函数指针的“升级版”
实例:C/C++中的函数指针
#include<stdio.h>
typedef int(*Calc)(int a, int b);
int Add(int x, int y)
{
int result = x + y;
return result;
}
int Sub(int x, int y)
{
int result = x - y;
return result;
}
int main()
{
int x = 100;
int y = 200;
int z = 0;
Calc funcPoint1 = &Add;
Calc 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);
}
-
一切皆地址 1) 变量(数据)是以某个地址(变量名所对应的内存地址)为起点的一段内存中所存储的值; 2)函数(算法)是以某个地址(函数名所对应的内存地址)为起点的一段内存中所存储的一组机器语言指令。 -
直接调用与间接调用 1)直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行 —> 返回; 2)间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行 —> 返回。 -
Java 中没有与委托相对应的功能实体 -
委托的简单使用 1)Action 委托 2)Func 委托
class Program
{
static void Main(string[] args)
{
Calculator cal = new Calculator();
Action action = new Action(cal.Report);
cal.Report();
action.Invoke();
action();
Func<int, int, int> func1 = new Func<int, int, int>(cal.Add);
Func<int, int, int> func2 = new Func<int, int, int>(cal.Sub);
int x = 100;
int y = 200;
int z = 0;
z = func1.Invoke(x, y);
Console.WriteLine(z);
z = func2.Invoke(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++传统
- 注意声明委托的位置
避免写错地方结果声明成嵌套类型。 委托在名称空间体里进行声明,和其他类处在同一级别。如果在Program类里面声明委托Calc,则Calc是Program的嵌套类,在使用时需写成Program.Calc。 - 委托与所封装的方法必须“类型兼容”
下图的第一行是委托类型的声明,而后面四行是委托可以封装的方法。
注:返回值的数据类型一致;参数列表在个数和数据类型上一致(参数名不需要一样)
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 = 10;
double b = 2;
double c = 0;
c = calc1.Invoke(a, b);
Console.WriteLine("{0} + {1} = {2}", a, b, c);
c = calc2.Invoke(a, b);
Console.WriteLine("{0} - {1} = {2}", a, b, c);
c = calc3.Invoke(a, b);
Console.WriteLine("{0} * {1} = {2}", a, b, c);
c = calc4.Invoke(a, b);
Console.WriteLine("{0} / {1} = {2}", a, b, 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;
}
}
委托的一般使用
- 实例:把方法当做参数传给另一个方法
1)正确使用1:模板方法,“借用”指定的外部方法来产生结果。 相当于“填空题” 常位于代码中部 委托有返回值 2) 正常使用2:回调(callback)方法,调用指定的外部方法。 相当于“流水线” 常位于代码末尾 委托无返回值
举例: 模板方法
public delegate double Calc(double x, double y);
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
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;
}
}
回调方法
public delegate double Calc(double x, double y);
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
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 Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class Logger
{
public void Log(Product product)
{
Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
}
}
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;
}
}
- 注意:难精通 + 易使用 + 功能强大的东西,一旦被滥用则后果非常严重
1)缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎; 2)缺点2:使可读性下降、debug的难度增加; 3)缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护; 4)缺点4:委托使用不当有可能造成内存泄漏和程序性能下降。
委托的高级使用
- 多播(multicast)委托
一个委托内部封装不止一个方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Invoke();
action2.Invoke();
action3.Invoke();
action1 += action2;
action1 += action3;
action1.Invoke();
}
}
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(1000);
}
}
}
}
- 隐式异步调用
1)同步与异步的简介 中英文的语言差异 (1)同步:你做完了我(在你的基础上)接着做 (2)异步:咱们两个同时做(相当于汉语中的“同步进行”)
2)同步调用与异步调用的对比 (1)每一个运行的程序是一个进程(process) (2)每个进程可以有一个或多个线程(thread),第一个运行的线程是主线程 (3)同步调用是在同一线程内 (4)异步调用的底层机理是多线程 (5)串行----同步----单线程,并行----异步----多线程
注意:异步调用中所说的“互不相干”指的是逻辑上,而现实工作当中经常会遇到多个线程共享(即同时访问)同一个资源(比如某个变量)的情况,这时候如果处理不当就会产生线程间争夺资源的冲突。
3)隐式多线程 v.s. 显式多线程 (1)直接同步调用:使用方法名
stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();
(2)间接同步调用:使用单播/多播委托的 Invoke 方法
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.Invoke();
action2.Invoke();
action3.Invoke();
action1 += action2;
action1 += action3;
action1.Invoke();
(3)隐式异步调用:使用委托的 BeginInvoke
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);
(4)显式异步调用:使用 Thread 或 Task
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();
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();
完整代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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();
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.Invoke();
action2.Invoke();
action3.Invoke();
action1 += action2;
action1 += action3;
action1.Invoke();
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
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();
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(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 {0} doing homework {1} hours(s).",this.ID,i);
Thread.Sleep(1000);
}
}
}
}
异步调用会出现资源冲突
- 应该适时地使用接口(interface)取代一些对委托的使用
Java 完全地使用接口取代了委托的功能,即 Java 没有 C# 中委托相对应的功能实体
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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);
Console.WriteLine(box2.Product.Name);
}
}
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 = "Toy Car";
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;
}
}
}
|