1、委托的本质
在调用委托我们是定义-》new实例,很让人以为是调用一个方法。实际上委托delegate就是一个类,类继承于FCL中定义的Systme.MulticastDelegate类型,所有委托类型都派生于MulticastDelegate,该类中还定义了四个方法,一个构造函数,Invoke方法,还有就是两个异步方法BeginInvoke和EndInvoke方法。委托可以理解为方法的“外号”。我们在IDE中定义一个委托类型时,最终是通过编译器将定义的代码转化为中间语言IL,然后再执行中间语言中的代码来转化为本机代码的,所以在Visual Studio中编写的代码只是一个包装而已,真真程序执行的是中间语言中的代码的。
由于所有委托类型都是继承于MulticastDelegate,MulticastDelegate又继承于Delegate,所以委托类型继承了MulticastDelegate的字段、属性和方法,在这些成员中,有三个非公共字段与后面专题要介绍的委托链有关,所以在这里先列出来的。 在程序中静态实例化和方法实例化不太一样。 其中第一个_Target静态实例化是null。方式实例化,那是个对象。记住第三个参数invocationList是个委托数组,用于存储委托,下面就讲。2、委托数组
委托链也是一个委托,只是因为它是把多个委托链在一起,所以我们就以委托链来这么称呼它的。
每次调用委托链时,委托链包装的每个方法都会顺序被执行,如果委托链中被调用的委托抛出一个异常,这样链中的后续所有对象都不能被调用,并且如果委托的前面具有一个非void的返回类型,则只有最后一个返回值会被保留,其他所有回调方法的返回值都会被舍弃,这就意味着其他所有操作的返回值都永远看不到的吗? 事实却不是这样的,我们可以通过调用Delegate.GetInvocationList方法来显式调用链中的每一个委托,同时可以添加一些自己的定义输出。 GetInvocationList方法返回一个由Delegate引用构成的数组,其中每一个数组都指向链中的一个委托对象。在内部,GetInvocationList创建并初始化一个数组,让数据的每一个元素都引用链中的一个委托,然后返回对该数组的一个引用。如果_invocatinList字段为null,返回的数组只有一个元素,该元素就是委托实例本身。下面就通过一个程序来演示下的:namespace DelegateChainDemo{ class Program { // 声明一个委托类型,它的实例引用一个方法 // 该方法回去一个int 参数,返回void类型 public delegate string DelegateTest(); static void Main(string[] args) { // 用静态方法来实例化委托 DelegateTest dtstatic = new DelegateTest(Program.method1); // 用实例方法来实例化委托 DelegateTest dtinstance = new DelegateTest(new Program().method2); DelegateTest dtinstance2 = new DelegateTest(new Program().method3); // 定义一个委托链对象,一开始初始化为null,就是不代表任何方法(我就是我,我不代表任何人) DelegateTest delegatechain = null; delegatechain += dtstatic; delegatechain += dtinstance; delegatechain += dtinstance2; ////delegatechain =(DelegateTest)Delegate.Remove(delegatechain,new DelegateTest(method1)); ////delegatechain = (DelegateTest)Delegate.Remove(delegatechain, new DelegateTest(new Program().method2)); Console.WriteLine(Test(delegatechain)); Console.Read(); } private static string method1() { return "这是静态方法1"; } private string method2() { throw new Exception("抛出了一个异常"); } private string method3() { return "这是实例方法3"; } // 测试调用委托的方法 private static string Test(DelegateTest chain) { if (chain == null) { return null; } // 用这个变量来保存输出的字符串 StringBuilder returnstring = new StringBuilder(); // 获取一个委托数组,其中每个元素都引用链中的委托 Delegate[] delegatearray=chain.GetInvocationList(); // 遍历数组中的每个委托 foreach (DelegateTest t in delegatearray) { try { //调用委托获得返回值 returnstring.Append(t() + Environment.NewLine); } catch (Exception e) { returnstring.AppendFormat("异常从 {0} 方法中抛出, 异常信息为:{1}{2}", t.Method.Name, e.Message, Environment.NewLine); } } // 把结果返回给调用者 return returnstring.ToString(); } }}
Remove方法被调用时,它会扫描delegateChain(第一个参数)所引用的委托对象内部维护的委托数组(如果对于委托数组为空的情况下调用Remove方法将不会有任何作用,就是不会删除任何委托引用,这里主要是说明扫描是从委托数组里进行扫描),如果找到delegateChain引用的委托对象的_target和_methodPtr字段
和第二个参数(新创建的委托)中的字段匹配的委托,如果删除之后数组中只剩下一个数据项时,就返回那个数据项(而不会去新建一个委托对象再初始化的,此时的_invocationList为null,而不是保存一个委托对象引用的数组了,具体可以Remove一个后调试看看的),如果此时数组中还剩余多个数据项,就新建一个委托对象——其中创建并初始化_invocationList数组(此时的数组引用的委托对象已经少了一个了,因为用Remove方法删除了)