委托其实之前早有接触过,但是没有系统的学习过,在工作中也没有主动尝试使用委托。C和C++可以利用函数指针,将可执行的步骤(函数)做为参数传递传递给另一个方法。C#中可以使用委托提供同样的功能。举一个简单的例子,生成订单时通知用户,可能是邮件通知,也可能是短信通知,也可能是其他形式的通知。下面给出一个基本的实现:
1 //通知枚举 2 public enum Notice 3 { 4 Message, 5 Emial, 6 QQ 7 } 8 static void Main(string[] args) 9 { 10 SendNotice("短信通知:*****", Notice.Message); 11 SendNotice("邮件通知:*****", Notice.Emial); 12 Console.ReadLine(); 13 } 14 15 public static void SendNotice(string content,Notice notice) 16 { 17 switch (notice) 18 { 19 case Notice.Message: 20 SendMessage(content);break; 21 case Notice.Emial: 22 SendEmial(content);break; 23 case Notice.QQ: 24 SendQQ(content);break; 25 } 26 } 27 28 29 //短信通知 30 public static void SendMessage(string content) 31 { 32 Console.WriteLine(content); 33 } 34 35 //邮件通知 36 public static void SendEmial(string content) 37 { 38 Console.WriteLine(content); 39 } 40 41 //QQ通知 42 public static void SendQQ(string content) 43 { 44 Console.WriteLine(content); 45 }
这个实现中有个 SendNotice方法,包含确定通知形式的逻辑,显然随着通知形式的变化,这里需要做修改,根据面向对象的思想,如果将变化的通知形式分装起来,做为对象一样传入SendNotice方法,便可以解决这个问题。这里就要用到了委托。
委托的使用
委托是什么?委托是一种引用方法的类型,通过委托,我们可以将一个方法当作对象封装起来,并且在运行时,我们可以通过这个对象来完成方法的调用。也就是说一旦为委托分配了方法,委托将与该方法具有相同的行为。
具体的委托的使用可以分为:委托声明,委托实例化,委托调用。
下面给出上面的例子的委托实现:
//委托声明:适用于一个无返回值,string类型入参的方法 public delegate void DelegateSample(string content); static void Main(string[] args) { SendNotice("短信通知:*****", SendMessage); SendNotice("邮件通知:*****", SendEmial); Console.ReadLine(); } //委托类型参数 DelegateSample sample public static void SendNotice(string content, DelegateSample sample) { sample(content); } //短信通知 public static void SendMessage(string content) { Console.WriteLine(content); } //邮件通知 public static void SendEmial(string content) { Console.WriteLine(content); } public static void SendQQ(string content) { Console.WriteLine(content); }
以上通过将声明一个DelegateSample类型委托,封装了具体发送通知这个过程,将方法SendEmial,SendMessage,SendEmial可以以参数形式传入SendNotice方法。PS:这里想到了简单工厂模式,工场中包含了实现派生类的具体逻辑,随着派生类的增加,工厂也就违背了“开放封闭原则”,此处用委托也是可以实现的。
上面的代码中只有委托的声明:
public delegate void DelegateSample(string content);
而在调用的时候直接将函数名传入方法SendNotice,并没有委托的实例化。事实上,这里只是没有使用new关键字显示实例化,当传入方法名,编译器会根据方法名来查找方法签名,并验证它同委托的参数类型是否匹配。
委托的实例化:
DelegateSample sample = new DelegateSample(SendEmial); //也可以这样写 DelegateSample sample1 = SendEmial;
委托调用:
sample.Invoke("调用委托");
委托链
委托链故名思议就是由委托对象构成的一个集合,利用委托连,可调用集合中的委托所代表的全部方法。为了方便C#开发人员,C#编译器自动为委托类型的实例重载了+=和-=操作符,这两个操作符分别调用了Delegate.Combine和Delegate.Remove方法。
static void Main(string[] args) { DelegateSample x = SendQQ; x += SendEmial; DelegateSample sample = new DelegateSample(SendQQ); sample += SendEmial; sample += SendMessage; DelegateSample s = SendEmial;//与 sample 链中SendEmial同一个对象 sample.Invoke("显式调用通知"); SendNotice("隐式QQ通知:*****", x); Console.ReadLine(); }
调用结果:
可以看出以上对委托链的调用时顺序经行的,这就存在局限性,即委托链中有一个方法抛异常或阻塞相当长的一段时间,后续委托对象可能就无法调用了。而实际上还存在另外一个问题,如果委托链中的委托对象是有返回值得方法引用,那么最后被调用的委托对象的返回值,其他所有回调方法的返回值都会被丢弃。对于这两个问题,C#提供了一个实例放啊GetInvocationList,用于显式调用委托链中的每一个委托,此方法返回一个由Delegete构成的数组,每个引用都指向委托链中的一个委托对象。具体使用:
public delegate void DelegateSample(string content); static void Main(string[] args) { DelegateSample sample = new DelegateSample(SendQQ); sample += SendEmial; sample += SendMessage; foreach (DelegateSample item in sample.GetInvocationList()) { try { //调用委托,获取返回值等处理 item("遍历调用"); } catch (Exception e) { //异常处理 Console.WriteLine("执行方法{0}有异常", item.Method.Name); } } Console.ReadLine(); } public static void SendMessage(string content) { Console.WriteLine(content); } public static void SendEmial(string content) { throw new Exception("抛出了一个异常"); //Console.WriteLine(content); } public static void SendQQ(string content) { Console.WriteLine(content); }
执行结果:
泛型委托
泛型委托其实就是含有泛型参数的委托。.Net Framework已经为我们提供了以下集中常见的委托
Action:允许回调一个无参数无返回值类型的方法。
Action<T>:允许回调一个 有入参无返回值类型的方法。参数个数为1-16个。
Func<T>:允许回调用有参数有返回值的方法。
Predicate<T>:允许回调有入参,返回值为bool型的方法。
泛型委托的基本使用:
static void Main(string[] args) { SendNotice("无返回值泛型委托Action<T>***", SendMessage); Console.WriteLine("有参数有返回值泛型委托Func<T>***求和{0}", Result(2, 3, Sum)); Console.WriteLine("返回值始终为bool型的泛型委托Predicate<T>***最大?{0}", IsMax(3, 4)); Console.ReadLine(); } public static int Sum(int a, int b) { return a + b; } public static bool IsMax(int a, int b) { return a > b; } public static void SendNotice(string content, Action<string> sample) { sample(content); } public static int Result(int a, int b, Func<int, int, int> func) { return func(a, b); } public static void SendMessage(string content) { Console.WriteLine(content); }
委托和反射
个别情况下开发人员在编译时可能不知道回调方法需要多少参数以及参数类型等信息,而.Net Framework已经为我们提供了方法CreateDelegate和DynamicInvoke来创建并调用委托。
CreateDelegate方法提供了多重重载,所有该方法的构造都是从Delegate派生的一个类型的新对象,具体参数类型有第一个参数type来标示,MethodInfo参数指出应该回调的方法。
下面是一个基本的创建委托并调用的例子:
static void Main(string[] args) { //将delType参数转换为一个委托类型 Type delType = Type.GetType("PracticeDemo.TwoInt32s"); if (delType == null) { Console.WriteLine("Invalid delType argument:" + args[0]); return; } //delType.GetMethods(); Delegate d; try { //将Arg1参数转换为一个方法 MethodInfo mi = typeof(Program).GetMethod("Add", BindingFlags.NonPublic | BindingFlags.Static); //创建包装了静态方法的一个委托对象 d = Delegate.CreateDelegate(delType, mi); Console.WriteLine("调用委托求和结果:{0}", d.DynamicInvoke(1, 3)); } catch (ArgumentException e) { return; } Console.ReadLine(); } private static Object Add(Int32 n1, Int32 n2) { return n1 + n2; }