委托,delegate,说白了也就是个语法糖.没有他我们可以写程序,有了他我们可以写出更好的程序.
delegate void Feedback(int value);
方法签名之前前加上 delegate 关键字,我们就定义了一个最简单的委托.但,事实上编译器为我们做了另一件事:创建委托类.人肉解压缩一下:
//这里就是整个委托最为核心的内容,委托 = 封装了的类 + Invoke方法
class Feedback : System.MulticastDelegate { public Feedback(Object obj,IntPtr method); public virtual void Invoke(int value); public virtual IAsyncResult BeginInvoke(int value, AsyncCallback callback,Object obj); public virtual void EndInvoke(IAsyncResult result); }
注意生成的 Invoke 方法,这个方法的和原来使用 delegate 创建的 方法签名 原型是一致的. 可以说 委托 并不是一种新的操作或者行为之类的.他只是简化了以前我们需要通过手动编码的复杂程度.原来需要编写一个类来进行操作的步骤,现在由编译器帮我们完成了,这个就是语法糖,这个就是委托.
多播委托,或者委托链,这个是什么,其实就是在 编译器 创建的 Feedback 类中加入的一个数组,如果有多个委托方法连接到一个委托上,执行的时候就相当于 Foreach 这个数组,每个方法 执行一下他的 Invoke. 这个数组放在哪里,其实就在继承的 System.MulticastDelegate 中,他有一个私有成员变量, _invocationList,这是个数组,每次增加或者删除一个委托就是在进行维护这个 委托 数组的操作.
//多播委托的 Invoke 方法执行类似下面的伪代码 public void Invoke(int value) { Delegate[] delegateSet = _invocationList as Delegate[]; if(delegateSet != null) { // 委托数组不为null,证明是一个委托链 foreach(Feedback d in delegateSet) d(value); //或者 d.Invoke(value) } else { //不为空,则调用原始的回调方法 _methodPtr.Invoke(_target, value); //逻辑近似实际代码,但实际发生的事情 C# 是表示不出来的 } }
另:可以通过 GetInvocationList()方法来获取多播委托绑定的成员.
泛型委托,就是系统中的 Action 和 Func,你可以在MSCorLib.dll 和 System.Core.dll 中找到他们,建议使用这些委托类型而不是定义更多的类型.
public delegate void Action(); public delegate void Action<T>(T obj); public delegate void Action<T1, T2>(T1 arg1, T2 arg2); public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2,T3 arg3); ... public delegate TResult Func<TResult>(); public delegate TResult Func<T,TResult>(T obj); public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2); public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2,T3 arg3); ...
C#为委托提供了很多简化的语法,比如委托连的 += 和 -= 操作实际上是Delegate类中 Combine 和 Remove 方法的简化, lambda 表达式实际上是匿名方法的一种简化,匿名方法实际上在编译的时候由编译器自己生成了一个方法 ... 这些简化很有效的提高了我们的开发效率,但也让一部分开发者感到迷茫.就如一句话所说:知道真相使你自由.明白其中的原理也会让你更合理的使用这些语法糖,而不至于迷失在糖罐中.
简化语法1: 不需要构造委托对象
button1.Click += new EventHandler(button1_Click); //可以简化为 button1.Click += button1_Click;
实际上,第二种情况下在编译的时候会由编译器自行加上包装器.
简化语法2: 不需要定义回调方法
可以理解为lambda 表达式和匿名函数,编译的时候也是由编译器"解压"为正常的方法和类.
简化语法3: 局部变量不需要手动包装到类中即可传给回调方法
Jeffrey Richter的一个小规则:如果需要在回调的方法中包含3行以上的代码,就不使用 lambda表达式.
委托和反射,两个重要的方法:
Delegate del = Delegate.CreateDelegate(...); del.DynamicInvoke(...) //伪代码,不多解释,只是说明方法名和所在的位置