委托详解
什么是委托
C/C++中的间接调用函数的示例:
#include<stdio.h>
/* 声明函数指针
* 并且将这种函数指针声明为一种类型
* 使用函数指针,间接调用函数
*/
typedef int (*Calc)(int a, int b);
int Add(int a, int b){
int res = a + b;
return res;
}
int Sub(int a, int b){
int res = a - b;
return res;
}
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);
return 0;
}
C#类库中自带的委托示例:
namespace DelegateExample {
internal class Program {
static void Main(string[] args) {
Calculator calculator = new Calculator();
//Action委托适用于无返回类型的方法
Action action = new Action(calculator.Report);
//直接调用Report方法
calculator.Report();
//间接调用
action.Invoke();
//模仿函数指针的形式
action();
//Func委托适用于有返回类型的方法
//前面两个int 是参数
//最后一个int 是返回类型
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);
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;
}
}
}
委托的特性:
- 引用方法: 委托允许存储对方法的引用,使得方法可以被动态地调用。
- 类型安全: 委托是类型安全的,它们在编译时会检查方法签名,确保委托实例只能引用与其声明的相同签名的方法。
- 多播性: 委托支持多播,即一个委托实例可以引用多个方法。通过
+=
和-=
操作符,可以动态地添加或移除委托链中的方法。 - 异步编程: 委托在异步编程中扮演了重要的角色,尤其是在使用
BeginInvoke
和EndInvoke
进行异步操作时。 - 匿名方法和 Lambda 表达式: C# 支持使用匿名方法和 Lambda 表达式来创建简洁的委托实例,减少了样板代码的编写。
- 委托泛型化: C# 提供了泛型委托
Action
和Func
,分别用于表示没有返回值和有返回值的委托,减少了需要定义新委托类型的情况。
委托的优势:
委托在调用方法是展现了很好的灵活性,
其次,可以使用委托来创建需要顺序调用的方法队列。而执行队列操作在服务中又非常常见,给我们带来更好的伸缩性。
另一个优势则在于允许多个操作并行执行,委托提供在不同线程上异步操作的内置支持,提高的响应能力。
但是,最重要的好处是,在实现事件时,在不了解彼此的不同对象之间发送消息。
委托的声明(自定义委托)
声明格式是仿照函数指针的格式
using System.Security.Claims;
namespace DelegateExample {
public delegate double Calc(double x, double y);
internal 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.Invoke(a, b);
Console.WriteLine(c);
c = calc2.Invoke(a, b);
Console.WriteLine(c);
c = calc3.Invoke(a, b);
Console.WriteLine(c);
c = calc4.Invoke(a, b);
Console.WriteLine(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;
}
}
}
委托对象的构造
委托对象通常可采用两种方式进行构造,一种是提供委托将封装的方法的名称,另一种是使用 lambda 表达式。
声明了一个名为Callback
的委托,该委托可以封装采用字符串作为参数并返回void
的方法:
public delegate void Callback(string message);
-
提供方法名称:
-
假设有一个名为
DelegateMethod
的方法,实例化委托如下:Callback handler = DelegateMethod; public static void DelegateMethod(string message) { Console.WriteLine(message); }
-
-
使用Lambda表达式:
-
以下代码使用Lambda表达式实例化了相同的委托:
Callback handler = (message) => Console.WriteLine(message);
-
这两种方式的作用都是相同的。
委托的使用
示例:
模版方法和回调方法:
都是用委托类型的参数,封装一个外部的方法,然后把这个方法传进另一个方法的内部,再进行间接调用。
这就是委托的常规用法
using System.Security.Claims;
namespace DelegateExample {
public delegate double Calc(double x, double y);
internal class Program {
static void Main(string[] args) {
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Logger logger = new Logger();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product> (productFactory.MakeToyCar);
Action<Product> act1 = new Action<Product>(logger.Log);
Box box1 = wrapFactory.WrapProduct(func1, act1);
Box box2 = wrapFactory.WrapProduct(func2, act1);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
//记录程序的运行状态
class Logger {
public void Log(Product product) {
Console.WriteLine($"Product{product.Name} created at {DateTime.UtcNow}. Price is {product.Price}");
//UtcNow是不带时区的时间
}
}
class Product {
public string Name { get; set; }
public double Price { get; set; }
}
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();
//如果产品的价格大于50就记录一下
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;
}
}
}
滥用的示例
using System.Security.Claims;
namespace DelegateExample {
internal class Program {
static void Main(string[] args) {
Operation opt1 = new Operation();
Operation opt2 = new Operation();
Operation opt3 = new Operation();
opt3.InnerOperation = opt2;
opt2.InnerOperation = opt1;
opt3.Operate(new object(), null, null);
/* 问题1:如果传入的两个参数为null,失败和成功的效果是什么?
* 答:内层的操作会调用外层的回调!
* 问题2:如果传入的两个参数不为null,会出现什么情况?
* 答:所有默认的callback都会被“穿透性”屏蔽
*
*/
}
}
class Operation {
public Action DefaultSuccessCallback { get; set; }
public Action DefaultFailureCallback { get; set;}
public Operation InnerOperation { get; set; }
public Object Operate(Object input, Action successCallback, Action failureCallback){
if(successCallback == null) {
successCallback = this.DefaultSuccessCallback;
}
if(failureCallback == null) {
failureCallback = this.DefaultFailureCallback;
}
Object result = null;
try {
result = this.InnerOperation.Operate(input, successCallback, failureCallback);
// Do something here
} catch {
failureCallback.Invoke();
}
successCallback.Invoke();
return result;
}
}
}
委托的高级使用
多播委托
多播委托:即一个委托的内部封装的不止一个方法,封装的多个方法的执行顺序,由方法封装的先后顺序决定
using System.Threading;
namespace MulticastDelegateExample {
internal 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 act1 = new Action(stu1.DoHomework);
Action act2 = new Action(stu2.DoHomework);
Action act3 = new Action(stu3.DoHomework);
//act1.Invoke();
//act2.Invoke();
//act3.Invoke();
//一个委托封装一个方法的形式,就是单播委托
//这是同步调用
//多播委托:
//将act2的方法也封装到act1中
act1 += act3;
act1 += act2;
act1.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 {this.ID} doing homework {i} hour(s).");
Thread.Sleep(1000);//让线程暂停一秒
}
}
}
}
同步调用与异步调用
同步调用:
using System.Threading;
namespace MulticastDelegateExample {
internal 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 act1 = new Action(stu1.DoHomework);
Action act2 = new Action(stu2.DoHomework);
Action act3 = new Action(stu3.DoHomework);
act1.Invoke();
act2.Invoke();
act3.Invoke();
for (int i = 0; i < 10; i++) {
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {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 {this.ID} doing homework {i} hour(s).");
Thread.Sleep(1000);//让线程暂停一秒
}
}
}
}
异步调用
BeginInvoke
方法会生成一个分支线程,在这个分支线程中再调用封装的方法
当多个线程访问同一个资源的时候,就有可能在争抢资源的时候发生冲突。
为了避免冲突,我们会为线程加锁,后面才学的高阶内容。
隐式的异步调用,不知道为什么运行失败了
using System.Threading;
namespace MulticastDelegateExample {
internal 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 act1 = new Action(stu1.DoHomework);
Action act2 = new Action(stu2.DoHomework);
Action act3 = new Action(stu3.DoHomework);
/* 隐式异步调用
* 这个方法要传两个参数,
* 第一个参数是回调方法,也就是当这个线程做完了,还需要做什么
* 第二个参数是Object,一般就为null
*/
act1.BeginInvoke(null, null);
act2.BeginInvoke(null, null);
act3.BeginInvoke(null, null);
for (int i = 0; i < 10; i++) {
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {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 {this.ID} doing homework {i} hour(s).");
Thread.Sleep(1000);//让线程暂停一秒
}
}
}
}
显式的异步调用:
古老的办法:
using System.Threading;
namespace MulticastDelegateExample {
internal 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};
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 < 10; i++) {
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"Main thread {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 {this.ID} doing homework {i} hour(s).");
Thread.Sleep(1000);//让线程暂停一秒
}
}
}
}
更高级的办法
using System.Threading;
namespace MulticastDelegateExample {
internal 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};
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 {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 {this.ID} doing homework {i} hour(s).");
Thread.Sleep(1000);//让线程暂停一秒
}
}
}
}
使用接口代替委托
使用委托:
using System.Security.Claims;
namespace DelegateExample {
internal 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; }
public double Price { 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";
product.Price = 12;
return product;
}
public Product MakeToyCar() {
Product product = new Product();
product.Name = "Toy Car";
product.Price = 100;
return product;
}
}
}
使用接口:
using System.Security.Claims;
namespace DelegateExample {
internal 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";
product.Price = 12;
return product;
}
}
class ToyCarFactory : IProductFactory {
public Product Make() {
Product product = new Product();
product.Name = "Toy Car";
product.Price = 100;
return product;
}
}
class Product {
public string Name { get; set; }
public double Price { 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;
}
}
}
委托的综合示例
下面的示例演示如何声明、实例化和使用委托。
BookDB
类封装用来维护书籍数据库的书店数据库。 它公开一个方法ProcessPaperbackBooks
,用于在数据库中查找所有平装书并为每本书调用委托。- 使用的
delegate
类型名为ProcessBookCallback
。 Test
类使用此类打印平装书的书名和平均价格。
使用委托提升书店数据库和客户端代码之间的良好分隔功能。
客户端代码程序不知道如何存储书籍或书店代码如何查找平装书。
书店代码不知道它在找到平装书之后对其执行什么处理。
//包含处理书店数据库的类
namespace Bookstore {
using System.Collections;
//书结构体
public struct Book {
public string Title;
public string Author;
public decimal Price;
public bool Paperback;//是否是平装书
public Book(string title, string author, decimal price, bool paperback)
{
Title = title;
Author = author;
Price = price;
Paperback = paperback;
}
}
//处理书籍的委托
public delegate void ProcessBookCallback(Book book);
//书店数据库类
public class BookDB {
//书籍列表
ArrayList list = new ArrayList();
//向数据库添加书籍
public void AddBook(string title, string author, decimal price, bool paperback) {
list.Add(new Book(title, author, price, paperback));
}
//接受一个委托作为参数,并对每本平装书调用该委托
public void ProcessPaperbackBooks(ProcessBookCallback processBook) {
foreach (Book book in list) {
//遍历每一本书,只有平装书才调用委托
if (book.Paperback)
{
//调用委托
processBook(book);
}
}
}
}
}
namespace BookTestClient {
using Bookstore;
//用来计算书的总数和平均价格
class PriceTotaller {
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book) {
countBooks++;
priceBooks += book.Price;
}
internal decimal AveragePrice() {
return priceBooks / countBooks;
}
}
internal class Test {
static void PrintTitle(Book book) {
Console.WriteLine($" {book.Title}");
}
static void Main(string[] args) {
BookDB bookDB = new BookDB();
//初始化数据库
AddBooks(bookDB);
//平装书的书名有:
Console.WriteLine("Paperback Book Titles:");
//在数据库类中调用方法
//并传入一个打印书名的方法,作为委托、
//打印所有书的书名
bookDB.ProcessPaperbackBooks(PrintTitle);
//为了调用实例方法,创建实例
PriceTotaller totaller = new PriceTotaller();
//调用数据库中的方法,并传入一个实例方法,作为委托
//计算书的总数和总价格
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
//打印出平装书的平均价格
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
//初始化数据库
static void AddBooks(BookDB bookDB) {
bookDB.AddBook("The C Programming Language", "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0", "The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia", "Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert's Clues for the Clueless", "Scott Adams", 12.00m, true);
}
}
}
问题总结
当委托构造为封装实例方法时,委托将同时引用实例和方法。 委托不知道除其所封装方法以外的实例类型,因此委托可以引用任何类型的对象,只要该对象上有与委托签名匹配的方法。 当委托构造为封装静态方法时,委托仅引用方法。
这段话应该如何理解?
委托将同时引用实例和方法,是什么意思?
这是查到的解答:
- 委托类似于C++中的函数指针,但是委托是完全面向对象的,不同于C++指针,它会同时封装对象实例和方法。
- 当委托引用实例方法时,它不仅存储对方法入口点的引用,还存储对调用该方法的实例的引用