【C#语言入门】16. 委托详解
一、什么是委托
- 委托(delegate)是函数指针的“升级版”
- 一切皆地址
- 变量(数据)是以某个地址为起点的一段内存中所储存的值
- 函数(算法)是以某个地址为起点的一段内存中所存储的一组机器语言指令
- 直接调用与间接调用
- 直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行→返回
- 间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行→返回
- Java中没有与委托相对应的功能实体
- 委托的简单实用
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report);//使用了action这个委托指向了calculator.report这个方法
calculator.Report();
action.Invoke();
action();//简便写法
Func<int, int, int> func = new Func<int, int, int>(calculator.Add);
Func<int, int, int> func = new Func<int, int, int>(calculator.Sub);
int x = 100;
int y = 50;
int z = 0;
z = func1.Invoke(x, y);
//z = func1(x, y);//简便写法
Console.WriteLine(z);
z = func2.Invoke(x, y);
Console.WriteLine(z);
}
}
class Calculator
{
public void Report()
{
Console.WriteLine("I have three 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++传统
- 注意声明委托的位置,避免写错地方结果声明成嵌套类型
- 委托与所封装的方法必须“类型兼容”——》返回值的数据类型一致,参数列表在个数和数据类型上一致(参数名不需要一样)
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;
double b = 200;
double 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)
{
double result = a + b;
return result;
}
public double Sub(double a, double b)
{
double result = a - b;
return result;
}
public double Mul(double a, double b)
{
double result = a * b;
return result;
}
public double Div(double a, double b)
{
double result = a / b;
return result;
}
}
三、委托的一般使用
- 实例:把方法当做参数传给另一个方法
- 正确使用方法1:模版方法,“借用“指定的外部方法来产生结果,相当于”填空题“,常常位于代码的中部,且此类型委托有返回值。
- 正确使用方法2:回调(callback)方法,调用指定的外部方法,相当于“流水线”,常位于代码末尾,且此类型委托无返回值。
- 注意:难精通+易使用+功能强大,一旦被滥用则后果非常严重
- 缺点1:这是一种方法级别的紧耦合,实现工作中要谨慎使用
- 缺点2:使可读性下降、debug的难度增加
- 缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
- 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降
模板方法:product、box和warpfactory类都不需要再变化了,只要在productfactory增加即可。
Reuse,重复使用,也叫“复用”,代码的复用不但可以提高工作效率,还可以减少bug的引入,良好的复用结构式所有优秀软件所追求的共同目标之一。
internal class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WarpFactory warpFactory = new WarpFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Box box1 = warpFactory.WarpProduct(func1);
Box box2 = warpFactory.WarpProduct(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 WarpFactory
{
public Box WarpProduct(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;
}
}
回调方法:
internal class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WarpFactory warpFactory = new WarpFactory();
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 = warpFactory.WarpProduct(func1, log);
Box box2 = warpFactory.WarpProduct(func2, log);
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 WarpFactory
{
public Box WarpProduct(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 = 70;
return product;
}
}
四、委托的高级使用
- 多播(multicast)委托,一个委托内部封装多个方法
- 隐式异步调用
- 同步与异步的简介
- 中英文的语言差异
- 同步:你做完了我(在你的基础上)接着做
- 异步:咱们两个同时做完
- 同步调用与异步调用的对比
- 每一个运行的程序是一个进程(process)
- 每个进程可以有一个或者多个线程(thread)
- 同步调用是在同一线程内
- 异步调用的底层机理是多线程
- 串行同步单线程,并行异步多线程
- 隐式多线程 v.s. 显式多线程
- 直接同步调用:使用方法名
- 间接同步调用:使用单播/多播委托的Invoke方法
- 隐式异步调用:使用委托的BeginInvoke
- 显式异步调用:使用Thread或者Task
- 同步与异步的简介
- 应该适时地使用接口(interface)取代一些对委托的使用
- Java完全地使用接口取代了委托的功能,即Java没有与C#中委托相对应的功能实体
这是单播委托,一个学生对应一个委托。
internal class Class1
{
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();
}
}
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} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
这是多播委托,且多播委托的执行顺序是按照封装顺序来的。
internal class Class1
{
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;
action1 += action3;
action1.Invoke();
}
}
class Student
{
...
}
同步直接调用
internal class Class1
{
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<5 ; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
class Student
{
...
}
同步间接调用
internal class Class1
{
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();
for(int i = 0; i<5 ; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
class Student
{
...
}
隐式间接异步调用,但是几个线程同时访问同一个资源的时候容易产生冲突从而导致错误
internal class Class1
{
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<5 ; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
class Student
{
...
}
显式异步调用
internal class Class1
{
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));
thread1.Start();
thread2.Start();
thread3.Start();
for(int i = 0; i<5 ; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
class Student
{
...
}
更为高级的显式异步调用
internal class Class1
{
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));
for(int i = 0; i<5 ; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).",this.ID, i);
Thread.Sleep(1000);
}
}
}
class Student
{
...
}