目录:
- 什么是委托
- 委托的作用
- 委托的本质
- 委托链
- 委托链返回值
一、什么是委托?
委托:将符合规则的方法进行包装。装载方法引用的盒子。将方法作为参数传递。
class Program { static void Main(string[] args) { StaticDelegateDome(); } //定义没有返回值委托Feedback,需要一个数值类型参数 internal delegate void Feedback(Int32 value); //方法群:静态、静态、实例 private static void FeedbackToConsole(Int32 value) { Console.WriteLine("这是【静态方法】文本显示=" + value); } private static void FeedbackToMsgBox(Int32 value) { Console.WriteLine("这是【这是静态方法】消息框显示=" + value); } private void FeedbackToFile(Int32 value) { Console.WriteLine("这是【实例方法】文件显示=" + value); } //委托回掉方法 private static void Counter(Int32 from, Int32 to, Feedback fb) { for (Int32 val = from; val <= to; val++) { if (fb != null) fb(val); } } //单个静态方法注册 private static void StaticDelegateDome() { Console.WriteLine("---------------静态方法注册委托例子------------------"); Counter(1, 3, null);//没有注册回调方法 Counter(1, 3, new Feedback(Program.FeedbackToConsole));//注册了一个静态的文本显示回调方法 Counter(1, 3, new Feedback(FeedbackToMsgBox));//如果存在一个类里面可以将前面的类可以省略 Console.WriteLine(); Console.ReadKey(); } //单个实例方法注册 private static void InstanceDelegateDome() { Console.WriteLine("---------------实例方法注册委托例子------------------"); Program p = new Program(); Counter(1, 3, null);//没有注册回调方法 Counter(1, 3, new Feedback(p.FeedbackToFile));//注册一个实例方法 Console.WriteLine(); Console.ReadKey(); } //方法组注册委托形成委托链1 private static void ChainDelegateDome1(Program p) { Console.WriteLine("---------------------委托链例子--------------------"); Feedback fb1 = new Feedback(FeedbackToConsole); Feedback fb2 = new Feedback(FeedbackToMsgBox); Feedback fb3 = new Feedback(p.FeedbackToFile); Feedback fbChain = null; fbChain = (Feedback)Delegate.Combine(fbChain, fb1); fbChain = (Feedback)Delegate.Combine(fbChain, fb2); fbChain = (Feedback)Delegate.Combine(fbChain, fb3); Counter(1, 2, fbChain); Console.WriteLine("------------------移除委托链中的方法---------------"); fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox)); } //方法组注册委托形成委托链2 private static void ChainDelegateDome2(Program p) { Console.WriteLine("---------------------委托链例子--------------------"); Feedback fb1 = new Feedback(FeedbackToConsole); Feedback fb2 = new Feedback(FeedbackToMsgBox); Feedback fb3 = new Feedback(p.FeedbackToFile); Feedback fbChain = null; fbChain += fb1; fbChain += fb2; fbChain += fb3; Counter(1, 2, fbChain); Console.WriteLine("------------------移除委托链中的方法---------------"); fbChain -= new Feedback(FeedbackToMsgBox); } }
二、为什么委托是类?
看上去委托就是:
声明,一个delegate,或者约定一下规则:返回值、参数等。
internal delegate string PutHandler(string options);
三、委托本质
internal delegate string PutHandler(string options);
对于以上代码C#编译器和CLR会为我们做多少工作呢?
编译器看到上面的代码会翻译成:
internal class Feedback : System.MulticastDelegate { public Feedback(Object obj, IntPtr methodAddress); public virtual void Invoke(Int32 value); public virtual IAsyncResult BoeginInvoke(Int32 value, AsyncCallback callback, Object obj); public virtual void EndInvoke(IAsyncResult result); }
需要注意的是,委托就是类。从以上编译器给咱们生成的代码可以看出,所有的委托都派生自:MulticastDelegate,而MulticastDelegate又派生自Delegate。
而这里面有几个重要的属性需要注意:
字段 | 类型 | 说明 |
_target | System。Object |
当委托对象包装静态方法时,这个值为null。 当包装实力方法时,这个引用的是回调方法要操作的对象 |
_metordPtr | System.IntPtr | 一个内部整数值,CLR用它标记要回调的方法 |
_invocationList | System.Objec | 通常为null。在构造一个委托链时,它可以引用一个委托数组。 |
首先,这里我们先说一下这个委托构造器,它包含两个参数:一个对象引用obj,一个是要回调方法的整数值methedAddress。
我们会发现我么调用委托构造函数的时候传递的是一个值--静态方法引用或者实例方法引用。
疑问:这也能调用?能的话,又是怎样调用的呢?
答案:可以调用,那这是怎么做到的呢?----还是编译器。
上面,编译器根据我们定义的委托为我们翻译生成了这个委托的所有代码。进一步,编译器会分析我们的源码,确定我们应用的是哪个对象和哪个方法。
现在,编译器确定了:引用的对象Object和一个标识引用方法的特殊值IntPtr。
这里的,引用对象传给构造器的第一个参数obj,这个标识引用方法的特殊值IntPtr就会传给methodAddress。
对于静态方法这个obj=null。
与此同时,在构造器内部,这两个实参分别保存在_target和_metordPtr这两个私有字段中,_invocationList初始化为null。
所以,我们使用委托的实例化,其实就是一个编译器包装我们引用方法的过程,这个过程是编译器分析所引用的对象和特殊标记值,传给委托构造器,并分别存放在私有字段_target和_metordPtr中。
而MulticastDelegate又派生自Delegate,Delegate又包含两个公共属性Target和MetordPtr,这两个参数的取值自然而然就是上面提到的MulticastDelegate中的私有字段_target和_metordPtr。
举例说明:
Feedback fb1 = new Feedback(FeedbackToConsole); Feedback fb3 = new Feedback(p.FeedbackToFile);
我们实例化以上委托,一个是静态方法,最后一个是实例方法。
Delegate类中的Target和MetordPtr有公开了可以对两个字段的访问,我们可以利用这两个公开属性,访问使用。
1、判断委托引用的实例方法所在的对象,是不是和我们指定的type类型的类型一致。
传入委托引用和待查类型,确定这个委托引用的实例方法是否就在我们提供的这个类型里面的实例方法。
static Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d, Type type) { return ((d.Target != null) && d.Target.GetType() == type); }
在主函数中这样调用
Boolean b = DelegateRefersToInstanceMethodOfType(new Feedback(FeedbackToConsole),typeof(Program));
这个返回False,因为是静态方法Target=null。
2、判断回调方法是否与我们提供的方法名一致
传入回调方法和指定方法名
static Boolean DelegateRefersToInstanceMethodOfName(MulticastDelegate d, String methodName) { return (d.Method.Name == methodName); }
接下来,我们来看看委托是怎样回调注册的回调函数的。
我们上面这样定义过:
//委托回掉方法 private static void Counter(Int32 from, Int32 to, Feedback fb) { for (Int32 val = from; val <= to; val++) { if (fb != null) fb(val); } }
编译器检测到这是一个委托,所以他会调用委托的Invoke(Int32 value)方法。所以:fb(val); 等价于 fb.Invoke(val);
四、委托链
我们之前定义了一个委托链方法:
//方法组注册委托形成委托链1 private static void ChainDelegateDome1(Program p) { Console.WriteLine("---------------------委托链例子--------------------"); Feedback fb1 = new Feedback(FeedbackToConsole); Feedback fb2 = new Feedback(FeedbackToMsgBox); Feedback fb3 = new Feedback(p.FeedbackToFile); Feedback fbChain = null; fbChain = (Feedback)Delegate.Combine(fbChain, fb1); fbChain = (Feedback)Delegate.Combine(fbChain, fb2); fbChain = (Feedback)Delegate.Combine(fbChain, fb3); Counter(1, 2, fbChain); Console.WriteLine("------------------移除委托链中的方法---------------"); fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox)); }
首先,委托链是委托对象构成的集合,所以我们定义了fb1,fb2,fb3三个委托对象。
用Combine方法将这三个委托对象放到一个空白委托对象fbChain中,构造委托链。所以fbChain就是一个委托对象集合了。
执行:如果只有一个委托对象,fbChain据直接只想fb1的委托对象上。
fbChain = (Feedback)Delegate.Combine(fbChain, fb1);
再执行:现在是又串联了两个委托对象,这样,编译器就会新创建一个委托对象,其中_invocationList不为null,而是一个数组,一一对应串联绑定的委托对象。而fbChain就会指向这个新创建的委托对象。如下图:
fbChain = (Feedback)Delegate.Combine(fbChain, fb2);
fbChain = (Feedback)Delegate.Combine(fbChain, fb3);
当fb.Invoke(val);发现 _invocationList!=null ,就会循环遍历调用 _invocationList 里面的数组。
C#编译器中 Combine()可以用“+=”,Remove()可以用“-=”代替。
五、委托链的返回值
首先,如果都有返回值,只保留最后一个返回值。
如果中间一个调用出现了异常,后面的都就不能顺利调用。所以,委托链不是非常健壮和完善。
这里要用到:MulticastDelegate类的GetInvocationList()方法:显示调用委托链中的每一个委托,针对每个委托,可以自己定制自己需要的算法。
public override sealed Delegate[] GetInvocationList();
这样就可以调用GetInvocationList(),并附给一个委托数组:
Delegate[] deList = fbChain.GetInvocationList(); foreach (var l in deList) { //do sometyhing }
这样,我们就可以显示循环遍历委托链,每个方法的返回值进行处理。
总之,编译器替我们做了大量的翻译工作。