什么是委托?
#include
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 = 200;
int z = 0;
z = Add(x, y);
printf("%d+%d=%d", x, y, z);
z = Sub(x, y);
printf("%d-%d=%d", x, y, z);
system("pause");
return 0;
}
我们可以看到输出结果如下:
>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...
在这个例子里,是通过函数的名字,来调用,是直接调用
#include
typedef int (* Calc)(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 = 200;
int z = 0;
Calc funcPoint1 = &Add;
Calc funcPoint2 = ⋐
z = funcPoint1(x, y);
printf("%d+%d=%d", x, y, z);
z = funcPoint2(x, y);
printf("%d-%d=%d", x, y, z);
system("pause");
return 0;
}
我们可以看到输出结果如下:
>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...
可以看到输出结果是一样的,这就说明了间接调用和直接调用的效果是一样的,这就是C语言中的函数指针;
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Calculator calculator = new Calculator();
Action action = new Action(calculator.Report); // 注意这里没有圆括号,这里只需要方法名,而不是调用方法
calculator.Report(); // 直接调用
action.Invoke(); // 间接调用,模仿函数指针的写法
action(); // 间接调用,简洁的写法
// 参数,参数,返回类型
Func<int, int, int> func = 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 = func.Invoke(x, y);
Console.WriteLine(z);
z = func2.Invoke(x, y);
Console.WriteLine(z);
// 间接调用,简洁的写法
z = func(x, y);
Console.WriteLine(z);
z = func2(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;
}
}
}
运行上面的程序可以获得如下的输出:
I have 3 methods
I have 3 methods
I have 3 methods
300
-100
300
-100
下面这个例子是自定义委托的声明与使用;
namespace ConsoleHelloWorld
{
public delegate double Calc(double x, double y);
// delegate 是类,需要声明在名称空间体里面;
// public 是访问范围,delegate 是告诉编译器要声明一个委托
// 第一个 double 是目标方法的返回值类型
// 然后 Calc 是委托的名字
// 后面的圆括号里面是目标方法的参数列表
// 到此自定义委托类型声明完成
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 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;
}
}
}
当我们自定义委托的时候,需要注意几点:
上图可以看到,第一行是委托的声明,下面四行是与之兼容的方法;
在工作中,一般是把委托当做参数传到另一个方法里去,这样做的好处可以间接调用委托所封装的方法,形成一个动态调用方法的结构;
下面展示的使模板方法的使用:
namespace ConsoleHelloWorld
{
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
box.Product = product;
return box;
// 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
// 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
// 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
// 产品包装成箱子返回回来,极大地实现代码的重复使用
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Cat";
return product;
}
}
}
下面展示的是回调方法的使用:
namespace ConsoleHelloWorld
{
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 Logger
{
public void Log(Product product)
{
// Log 以回调的形式传进模版的方法里
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
{
public Box WrapProduct ( Func<Product> getProduct, Action<Product> logCallback)
{
// 模板方法
Box box = new Box();
// 执行传进来的委托所封装的方法,这就是间接调用
Product product = getProduct.Invoke(); // 获取产品,将产品装入 Box
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product;
return box;
// 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
// 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
// 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
// 产品包装成箱子返回回来,极大地实现代码的重复使用
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
product.Price = 20;
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Cat";
product.Price = 120;
return product;
}
}
}
无论是模版方法还是回调方法,都使用委托类型的参数封装一个外部的方法,然后把这个方法传进方法的内部进行间接调用, 这个就是委托的常规用法。
委托如果被滥用的后果非常危险;
多播委托指的是一个委托内部封装了不止一个方法,下面是例子:
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Student student1 = new Student() { Id = 1, PenColor = ConsoleColor.Yellow };
Student student2 = new Student() { Id = 2, PenColor = ConsoleColor.Green };
Student student3 = new Student() { Id = 3, PenColor = ConsoleColor.Red };
Action action1 = new Action(student1.DoHomework);
Action action2 = new Action(student2.DoHomework);
Action action3 = new Action(student3.DoHomework);
// 多播委托的写法:
action1 += action2; // 将 aciton2 合并到 action1
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.", this.Id, i);
Thread.Sleep(1000); // 线程暂停一秒钟
}
}
}
}
下面是同步调用的异步调用的例子
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Student student1 = new Student() { Id = 1, PenColor = ConsoleColor.Yellow };
Student student2 = new Student() { Id = 2, PenColor = ConsoleColor.Green };
Student student3 = new Student() { Id = 3, PenColor = ConsoleColor.Red };
// 直接同步调用
student1.DoHomework();
student2.DoHomework();
student3.DoHomework();
Console.WriteLine("=============================================");
Action action1 = new Action(student1.DoHomework);
Action action2 = new Action(student2.DoHomework);
Action action3 = new Action(student3.DoHomework);
// 使用委托的隐式异步调用
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
Console.WriteLine("=============================================");
// 使用委托的显式异步调用
Task task1 = new Task(new Action(student1.DoHomework));
Task task2 = new Task(new Action(student2.DoHomework));
Task task3 = new Task(new Action(student3.DoHomework));
task1.Start();
task2.Start();
task3.Start();
Console.WriteLine("=============================================");
// 单播委托的间接同步调用
action1.Invoke();
action2.Invoke();
action3.Invoke();
Console.WriteLine("=============================================");
// 多播委托的间接同步调用
action1 += action2;
action2 += action3;
action1();
Console.WriteLine("=============================================");
}
}
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.", this.Id, i);
Thread.Sleep(1000); // 线程暂停一秒钟
}
}
}
}
委托使用不当回提高代码的维护难度,使用接口可以避免这些不必要的麻烦还可以获得相同的功能;
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
IProductFactory pizzaFactory = new PizzaFactory();
IProductFactory toycarFactory = new ToyFactory();
WrapFactory wrapFactory = new WrapFactory();
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);
Box box1 = wrapFactory.WrapProduct(pizzaFactory, log);
Box box2 = wrapFactory.WrapProduct(toycarFactory, log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
interface IProductFactory
{
Product Make();
}
class PizzaFactory : IProductFactory // 这个类实现了IProductFactory的接口
{
public Product Make()
{
// 重构是指基本不改变原来的代码,只是把代码放到更合适的地方去
Product product = new Product();
product.Name = "Pizza";
product.Price = 20;
return product;
}
}
class ToyFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "Toy Cat";
product.Price = 120;
return product;
}
}
class Logger
{
public void Log(Product product)
{
// Log 以回调的形式传进模版的方法里
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
{
public Box WrapProduct(IProductFactory productFactory, Action<Product> logCallback)
{
// 模板方法
Box box = new Box();
Product product = productFactory.Make();
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product;
return box;
}
}
}
可以看到,重构之后,使用接口之后,程序没有委托的身影,也就没有方法级别的耦合;
这个例子说明可以使用接口取代委托;
下面是一个小例子
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
System.Timers.Timer timer = new System.Timers.Timer(); // 事件拥有者 timer
timer.Interval = 1000; // ms
Boy boy = new Boy(); // 事件的响应者是 boy 对象
Girl girl = new Girl();
timer.Elapsed += boy.Action;// += 是订阅的写法,后面要跟上事件响应者的事件处理器
timer.Elapsed += girl.Action;
// 事件 Elapsed,事件订阅 +=
timer.Start();
Console.ReadLine();
}
}
class Boy
{
// 事件的处理器
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Jump!");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Sing!");
}
}
}
上面展示的是一个事件同时有两个事件处理器的时候的样子;
上图展示的是标准的事件机制模型,结构清晰,是MVC、MVP等设计模式的雏形;
下面的程序是对这个标准的事件机制模型的解释
namespace WindowsFormsApp1
{
internal static class Program
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
Form form = new Form(); // 事件的拥有者 form
Controller controller = new Controller(form); // 事件的响应者 controller
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
this.form.Click += this.FormClicked; // 事件是 form 的 click,+=实现事件订阅
}
}
// 事件处理器
private void FormClicked(object sender, EventArgs e)
{
this.form.Text = DateTime.Now.ToString();
}
}
}
上图展示的是对象用自己的方法订阅、处理自己的事件;
下面的程序是对上图的解释,同时接触到了什么是派生
namespace WindowsFormsApp1
{
internal static class Program
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
MyForm form = new MyForm(); // 事件的拥有者 form,事件的响应者也是 from
form.Click += form.FormClicked; // 事件是 Click,事件的订阅是 +=
form.ShowDialog();
}
}
class MyForm : Form // 派生,集成原有的方法之外还可以添加新的方法
{
// 事件处理器
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.Now.ToString();
}
}
}
上图展示的是使用最多的,特点是,事件的拥有者是事件的响应者的字段成员,是Windows上默认的事件订阅和处理结构;
下面的程序是对上图示例的解释
namespace WindowsFormsApp1
{
internal static class Program
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
// 事件的响应者是 MyForm 的对象
class MyForm : Form
{
private TextBox textBox;
private Button button; // button 是事件的拥有者,且为字段成员
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();
// 显示在 form 当中
this.Controls.Add(this.textBox);
this.Controls.Add(this.button);
this.button.Click += this.ButtonClicked; // 事件是 Click
// += 是事件的订阅
this.button.Text = "Say Hello!";
this.button.Top = 100;
}
// 事件的处理器
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "Hello World";
}
}
}
完整的事件声明方式 示例
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); // 事件的拥有者
Waiter waiter = new Waiter(); // 事件的响应者
customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
// Order 事件 += 事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer // 需要保证访问级别是一致的
{
private OrderEventHandler orderEventHandler;
// 事件 Order
public event OrderEventHandler Order
{
add
{
this.orderEventHandler += value;
}
remove
{
this.orderEventHandler -= value;
}
}
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay ${0}", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit Dowm.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think.......");
Thread.Sleep(1000);
}
if (this.orderEventHandler != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.orderEventHandler.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn(); ;
this.SitDown();
this.Think();
}
}
// 事件的响应者
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
简略的事件声明方式 示例
namespace ConsoleHelloWorld
{
class Program
{
static void Main(string[] args)
{
Customer customer = new Customer(); // 事件的拥有者
Waiter waiter = new Waiter(); // 事件的响应者
customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
// Order 事件 += 事件的订阅
customer.Action();
customer.PayTheBill();
}
}
public class OrderEventArgs : EventArgs
{
public string DishName { get; set; }
public string Size { get; set; }
}
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class Customer // 需要保证访问级别是一致的
{
public event OrderEventHandler Order;
public double Bill { get; set; }
public void PayTheBill()
{
Console.WriteLine("I will pay ${0}", this.Bill);
}
public void WalkIn()
{
Console.WriteLine("Walk into the restaurant.");
}
public void SitDown()
{
Console.WriteLine("Sit Dowm.");
}
public void Think()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Let me think.......");
Thread.Sleep(1000);
}
if (this.Order != null)
{
OrderEventArgs e = new OrderEventArgs();
e.DishName = "Kongpao Chicken";
e.Size = "large";
this.Order.Invoke(this, e);
}
}
public void Action()
{
Console.ReadLine();
this.WalkIn(); ;
this.SitDown();
this.Think();
}
}
// 事件的响应者
public class Waiter
{
internal void Action(Customer customer, OrderEventArgs e)
{
Console.WriteLine("I will serve you the dish - {0}", e.DishName);
double price = 10;
switch (e.Size)
{
case "small":
price = price * 0.5;
break;
case "large":
price = price * 1.5;
break;
default:
break;
}
customer.Bill += price;
}
}
}
为什么有了委托类型的字段,还需要事件?