# C# 委托(Delegate)
C# 中的委托类似于 C 或 C++ 中函数的指针,但它是函数指针的 “升级版”。
委托是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变,可同时指向多个函数方法。
委托特别用于实现事件和回调方法。所有的委托都派生自 System.Delegate 类。
# 委托的声明
- 委托是一个类(类的声明可以在其他类的外面或里面)
- 类是数据类型,所以委托也是一种数据类型
- 声明的方式与一般的类不同
- 委托所封装的方法必须类型兼容
声明委托
| |
| public delegate string HelloDelegate(string message); |
委托所封装的方法必须类型兼容
| public delegate string HelloDelegate(string message); |
| string Hello(string message) {return message} |
| string Hello_1(string message) {return "abc"} |
# 委托的一般使用
- 实例:把方法当作参数传给另一个方法
- 正确使用 1:模板方法,“借用 " 指定的外部方法来产生结果
- 正确使用 2:回调 (callback) 方法,调用指定的外部方法
- 注意:难精通 + 易使用 + 功能强大东西,一旦被滥用,后果非常严重
- 缺点 1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
- 缺点 2:使可读性下降、debug 的难度增加
- 缺点 3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
- 缺点 4:委托使用不当有可能造成内存泄漏和程序性能下降
# 委托、泛型委托 (使用回调方法)
使用委托
| public delegate void HelloDelegate(string message); |
| private static void Main(string[] args) |
| { |
| HelloDelegate helloDelegate = new HelloDelegate(Hello); |
| |
| helloDelegate("Hello"); |
| } |
| |
| private static void Hello(string message) |
| { |
| Console.WriteLine(message); |
| } |
使用泛型委托
| public delegate void HelloDelegate2<T>(T message); |
| private static void Main(string[] args) |
| { |
| HelloDelegate2<float> helloDelegate2 = new HelloDelegate2<float>(Hello2<float>); |
| helloDelegate2(12f); |
| } |
| |
| private static void Hello2<T>(T message) |
| { |
| Console.WriteLine(message); |
| } |
官方版本(无返回值)
| private static void Main(string[] args) |
| { |
| Action<string> Act = new Action<string>(Hello3); |
| Act("官方版本(无返回值)"); |
| } |
| |
| private static void Hello3<T>(T X) |
| { |
| Console.WriteLine(X); |
| } |
官方版本(有返回值)
| private static void Main(string[] args) |
| { |
| Func<string, string> fun_1 = new Func<string, string>(Hello4); |
| Console.WriteLine(fun_1("官方版本(有返回值)")); |
| } |
| |
| private static string Hello4<T>(T arg) |
| { |
| return arg.ToString(); |
| } |
# 模板方法
| private static void Main(string[] args) |
| { |
| |
| FuncTest(1, 2, (a, b) => { return a == b; }); |
| |
| |
| |
| } |
| |
| private static void FuncTest<T>(T message_0, T message_1, Func<T, T, bool> message_2) |
| { |
| Console.WriteLine(message_0); |
| |
| if (message_2(message_0, message_1)) |
| { |
| Console.WriteLine(message_1); |
| } |
| } |
# 委托的高级使用
- 多播 (multicast) 委托
- 多线程
- 多线程不是本篇文章的重点,下面给出一些简单的介绍,详细内容请参阅其他文章。
- 同步与异步的简介
- 同步调用与异步调用的对比
- 每一个运行的程序是一个进程 (process)
- 每个进程可以有一个或者多个线程 (thread)
- 同步调用是在同一线程内
- 异步调用的底层机理是多线程
- 串行 = 同步 = 单线程,并行 = 异步 = 多线程
- 隐式多线程和显式多线程
- 直接同步调用:使用方法名
- 间接同步调用:使用单播或多播委托的 Invoke 方法
- 隐式异步调用:使用委托的 BeginInvoke
- 显式异步调用:使用 Thread 或 Task
- 应该适当地使用接口 (interface) 取代一些对委托的使用
- Java 完全地使用接口取代了委托的功能,所以 Java 没有与 C# 中委托相对应的功能实体
# 多播委托
- 每一个委托都是继承自 MulticastDelegate,也就是说,每一个委托都是多播委托
- 有返回值的多播委托只返回最后一个方法的值
- 多播委托可以用 += 和 -= 进行增加和减少操作
- 给委托传递相同方法时,生成的委托实例也是相同的(同一个实例)
- 给委托传递不同方法时,生成的委托实例也是不同的(不同的实例)
| public class DelegatesTest |
| { |
| private static void Main(string[] args) |
| { |
| MulticastDelegateTest M = new MulticastDelegateTest(); |
| M.Show_0(); |
| M.Show_1(); |
| M.Show_2(); |
| } |
| } |
| |
| public class MulticastDelegateTest |
| { |
| |
| |
| |
| public void Show_0() |
| { |
| Action action = new Action(MethodTest_0); |
| |
| |
| |
| |
| |
| |
| |
| action += MethodTest_1; |
| action += MethodTest_1; |
| |
| action -= MethodTest_1; |
| |
| action(); |
| } |
| |
| |
| |
| |
| public void Show_1() |
| { |
| Func<string> func = () => { return "lambda_0"; }; |
| |
| func += () => { return "lambda_1"; }; |
| func += () => { return "lambda_2"; }; |
| func -= () => { return "lambda_2"; }; |
| |
| |
| Console.WriteLine(func()); |
| } |
| |
| |
| |
| |
| public void Show_2() |
| { |
| Action action = new Action(MethodTest_0); |
| action += MethodTest_0; |
| action += MethodTest_0; |
| action += MethodTest_0; |
| |
| |
| Delegate[] delegates = action.GetInvocationList(); |
| foreach (Action item in delegates) |
| { |
| item(); |
| } |
| } |
| |
| public void MethodTest_0() |
| { |
| Console.WriteLine("MethodTest_0"); |
| } |
| |
| public void MethodTest_1() |
| { |
| Console.WriteLine("MethodTest_2"); |
| } |
| } |