首先弄清楚以下几个问题:
1,什么是委托?
委托是一个类(可以通过查看IL代码证明),而且是类型安全的。
委托对象相当于方法包装器,使方法能通过包装器进行间接回调。
2,使用委托的好处?
使用委托可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,提高了程序的可扩展性。
一,如何使用委托
代码如下:
namespace DelegateDemo1 { //1,声明委托 internal delegate void PrintMyName(string s); public class Test { public static void PrintChineseName(string name) { Console.WriteLine("My chinese name is:{0}",name); } public void PrintEnglishName(string name) { Console.WriteLine("My english name is:{0}", name); } } public class Program { static void Main(string[] args) { Test t = new Test(); //2,创建委托对象 PrintMyName pmn; pmn = new PrintMyName(Test.PrintChineseName);//或用快捷语法:PrintMyName pmn = Test.PrintChineseName //3,调用委托 if (pmn != null) { pmn("鲁宁"); } pmn = t.PrintEnglishName;//快捷语法 if (pmn != null) { pmn("Mcgrady"); } Console.ReadKey(); /*程序输出结果为: My chinese name is:鲁宁 My english name is:Mcgrady */ } } }
总结:
1,委托声明使用关键字delegate,与方法相似有返回值和签名,但没有方法体。
2,实例化委托对象时可以使用new运算符,也可以使用快捷语法,通常快捷语法更常用。
3,供委托对象包装的方法返回类型和签名必须与委托匹配。
4,调用委托与调用方法相似,但调用前最好判断委托对象的方法调用列表是否为空。
二,委托揭秘
先看一下编译器生成的代码,如图:
从这张图中我们可以得到如下信息:
1,委托的确是生成了一个类,同时也对文章开头的观点进行了验证。当编译器看到internal delegate void PrintMyName(string s);这行代码的时候就会自动定义一个完整的类,它包含四个方法,一个构造器,Invoke,BeginInvoke,EndInvoke。代码如下:
internal class PrintMyName : System.MulticastDelegate { //构造器 public PrintMyName(Object obj,IntPtr method); //这个方法和源代码指定的原型一样 public virtual void Invoke(string s); public virtual IAsyncResult BeginInvoke(string s,AsyncCallback callback,Object obj); public virtual void EndInvoke(IAsyncResult result); }
2,所有的委托都派生自System.MulticastDelegate类,而System.MulticastDelegate又派生自System.Delegate类。
其中MulticastDelegate中定义了三个非公共字段,分别是_target,_methodPtr,_invocationList。Delegate类中定义了两个只读的公共实例属性,分别是Target和Method。Target属性返回保存在MulticastDelegate中字段_target的值;Method属性返回保存在MulticastDelegate中字段_methodPtr的值。下面是实际应用的例子。
public class Program { static void Main(string[] args) { Test t = new Test(); PrintMyName pmn; pmn = new PrintMyName(Test.PrintChineseName);//或用快捷语法:PrintMyName pmn = Test.PrintChineseName Console.WriteLine("Target:{0},Method:{1}",pmn.Target,pmn.Method); pmn = t.PrintEnglishName; Console.WriteLine("Target:{0},Method:{1}", pmn.Target, pmn.Method); Console.ReadKey(); } }
程序输出结果:
注意:如果委托对象包装的是一个静态方法,Target将返回Null值,如果是实例方法,那么Target的值就是回调方法要操作的对象Test。
三,委托链
委托链是由委托对象构成的一个集合。利用委托链,可调用集合中的委托所包装的全部方法。对上面的例子加以修改来演示委托链,代码如下:
namespace DelegateDemo1 { internal delegate void PrintFunction(string s); public class Test { public static void PrintFunction1(string name) { Console.WriteLine("This is static method PrintFunction1,create by {0}", name); } public void PrintFunction2(string name) { Console.WriteLine("This is instance method PrintFunction2,create by {0}", name); } public void PrintFunction3(string name) { Console.WriteLine("This is instance method PrintFunction3,create by {0}", name); } } public class Program { static void Main(string[] args) { Test t = new Test(); PrintFunction pmn1 = new PrintFunction(Test.PrintFunction1); PrintFunction pmn2 = t.PrintFunction2; PrintFunction pmn3 = t.PrintFunction3; PrintFunction pmnChain = null;//定义变量引用委托链 pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn1);//调用Delegate类的静态方法Combine添加委托到委托链 pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn2); pmnChain = (PrintFunction)Delegate.Combine(pmnChain, pmn3); pmnChain("Mcgrady");//调用委托链 Console.WriteLine(); pmnChain = (PrintFunction)Delegate.Remove(pmnChain, pmn2);//调用调用Delegate类的静态方法Remove从委托链中移除委托 Console.WriteLine("*****The result as below after remove method of PrintFunction2*****"); pmnChain("Mcgrady"); Console.ReadKey(); } } }
程序输出结果:
总结:
1,构造委托链时,会调用Delegate类的两个静态方法Combine和Remove。C#的编译器自动为委托的实例重载了+=和-=操作符,这两个操作符分别调用Combine和Remove方法,所以我们的代码可以简化,如下面的代码。
public class Program { static void Main(string[] args) { Test t = new Test(); PrintFunction pfChain = null; pfChain += Test.PrintFunction1; pfChain += t.PrintFunction2; pfChain += t.PrintFunction3; pfChain("Mcgrady"); Console.WriteLine(); Console.WriteLine("*****The result as below after remove method of PrintFunction2*****"); pfChain -= t.PrintFunction2; pfChain("Mcgrady"); Console.ReadKey(); } }
输出结果与使用Delegate的静态方法Combine和Remove一样,这是因为编译器自动用Delegate类的Combine和Remove静态方法分别代替了+=和-=操作符(这一点可以从编译器生成的IL代码中得到验证)。
2,当构造委托链时,_invocationList字段会被初始化为引用一个委托对象的数组,这一点也非常重要。