在我的上一篇文章中简单的介绍了委托的定义以及使用方法,也探讨了一下定义一了委托,系统在编译的时候会自动的添加几个公共的方法,包括Invoke(),BeginInvoke(),EndInvoke()等,如果您不清楚,请点击这里,因为我们这篇文章主要写多路广播委托、委托协变以及如何创建一个泛型的委托,对于一些委托的基础,还是需要的。
多路广播,换句话说,一个委托对象可以维护一个调用方法的列表而不只是单独的一个方法。给一个委托对象添加多个方法时,不用直接分配,重载+=运算符即可。同样的,两个委托的对象也可以用+=来组合成一个多路广播委托的实例。下面还是用一个简单的例子来说明一下:
- namespace LinQ
- {
- //声明一个委托,这个委托可以指向任何传入两个整数并且返回一个整数的方法
- public delegate int MathOP(int i, int j);
- //这个类包含了MathOP将指向的方法
- class DelegateMath
- {
- public static int Add(int i, int j)
- {
- Console.WriteLine("{0}+{1}={2}",i,j,i+j);
- return i + j;
- }
- public static int Subtract(int i, int j)
- {
- Console.WriteLine("{0}-{1}={2}", i, j, i - j);
- return i - j;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- //声明一个委托
- MathOP o;
- //调用委托后的结果
- int result;
- //将委托关联到对象
- o = DelegateMath.Add;
- o += DelegateMath.Subtract;
- //使用委托间接调用该委托所关联的所有方法对象,并取得返回值
- result = o(10, 20);
- Console.WriteLine("result={0}", result);
- Console.WriteLine("-----------------");
- //在委托中移除一个方法对象
- o -= DelegateMath.Subtract;
- //再次使用委托间接调用该委托所关联的所有方法对象,并取得返回值
- result = o(10,20);
- Console.WriteLine("result={0}", result);
- Console.ReadLine();
- }
- }
- }
执行结果:
正如我们所料,分割线以上因为委托的方法列表里面关联了2个方法,所以输出了2个方法里面的内容,而分割线以下使用-=移除了一个成员,所以只执行了一个方法。而这个委托最终返回的结果是关联方法中的最后一个。其次,委托的+=和-=还可以用于两个委托。例如:
- namespace LinQ
- {
- //声明一个委托,这个委托可以指向任何传入两个整数并且返回一个整数的方法
- public delegate int MathOP(int i, int j);
- //这个类包含了MathOP将指向的方法
- class DelegateMath
- {
- public static int Add(int i, int j)
- {
- Console.WriteLine("{0}+{1}={2}",i,j,i+j);
- return i + j;
- }
- public static int Subtract(int i, int j)
- {
- Console.WriteLine("{0}-{1}={2}", i, j, i - j);
- return i - j;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- //声明一个委托
- MathOP o,a,b;
- //将委托关联到对象
- a = DelegateMath.Add;
- b = DelegateMath.Subtract;
- //两个同类型的委托做combine操作
- o = a + b;
- int result = o(10,20);
- Console.WriteLine("result={0}",result);
- Console.WriteLine("-----------------");
- //对委托做一个remove操作
- o = o - b;
- result = o(10, 20);
- Console.WriteLine("result={0}",result);
- Console.ReadLine();
- }
- }
- }
运行结果一样是:
好,多路广播委托就到这里了,关于+=和-=,其实是转换为一个对静态Delegate.Combine()和Delegate.Remove()方法的调用。下面贴一下经过编译后,+=和-=生成的IL代码,如果有兴趣的朋友可以自己去查一下含义:
+=:
class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
-=:
class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
--------------------------我是分割线----------------------------------
下面我们进入下一个话题,委托协变。
我们注意到了,之前创建的每个委托指向的方法都返回简单的数字类型或者没有返回类型,假如我们创建了一个父类,并且创建了一个返回值类型是父类的委托。如果我们从父类派生出了一个子类,那么之前建的委托能不能指向返回值类型是子类的方法呢?因为委托是安全类型,也就是说她不遵守继承的基本原则。在.NET2.0以前,这个委托是不可以指向一个返回值类型为子类型的方法的。但是.NET2.0以后出现了一个委托协变的概念,这正是我们现在要讨论的话题。
所谓协变,也称为宽松委托,我的理解是这样的:我们能够构建一个委托,能指向返回类以及相关继承体系的方法。简单点说就是假如一个委托指向了一个返回值类型是A的方法,而B又继承了A,那么同样的,委托也能够指向一个返回值类型是B的方法。下面继续贴一下代码说明:
- namespace LinQ
- {
- //父类
- public class Parent
- {
- public virtual void ShowWho() { Console.WriteLine("I'm Parent"); }
- }
- //子类
- public class Child : Parent
- {
- public override void ShowWho(){ Console.WriteLine("I'm Child"); }
- }
- //这个类包含了委托即将指向的方法
- class GetNew
- {
- public static Parent GetParent() { return new Parent(); }
- public static Child GetChild() { return new Child(); }
- }
- //声明一个委托,这个委托可以指向任何返回值类型为Parent,或者返回值类型是Parent派生出来的值类型的方法
- public delegate Parent MathOP();
- class Program
- {
- static void Main(string[] args)
- {
- //声明一个委托
- MathOP o;
- //将委托关联到对象
- o = new MathOP(GetNew.GetParent);
- Parent p = o();
- p.ShowWho();
- //协变允许这种目标对象赋值
- o = new MathOP(GetNew.GetChild);
- Child c = (Child)o();
- c.ShowWho();
- Console.ReadLine();
- }
- }
- }
运行结果:
如上例,委托类型定义为指向返回强类型的Parent的方法,但是由于协变,我们也可以指向返回它的派生类型的方法。为了得到派生类,仅仅做个显式的强制转换即可。
顺便说明一下,与协变类似,逆变 没错,就是逆变 ,允许我们创建一个委托,指向多个方法,方法的参数是存在传统继承关系的对象,这个与协变相似,就不多说了,有兴趣的朋友可以找资料自己学习一下。
--------------------------我是分割线----------------------------------
呼,终于进入今天的最后一个话题,创建泛型委托,在这时,QQ这货忽然弹出21:30视频直播曼联VS利物浦。这不是在催命么。我顶~
现在有这样一个场景,有2个方法,void StringTarget(string arg),以及void IntTarget(int arg),我们要定义委托去指向这两个方法,因为参数类型不同,而且也不是继承的关系对象,所以我们可以定义两个委托,分别用于指向两个方法,如public Delegate void StringDelegate(string arg)以及public Delegate void IntDelegate(int arg),然后分别new之后再指向方法;我们还可以创建一个泛型委托,下面来看代码:
- namespace LinQ
- {
- //这个类包含了委托即将指向的方法
- class DelegateTarget
- {
- public static void StringTarget(string arg) { Console.WriteLine("StringTarget:{0}",arg); }
- public static void IntTarget(int arg) { Console.WriteLine("IntTarget:{0}", arg); }
- }
- //声明一个委托,这个委托可以指向任何返回值类型为void并且接受单个参数的方法
- public delegate void MathOP<T>(T arg);
- class Program
- {
- static void Main(string[] args)
- {
- //注册目标
- MathOP<string> oString = new MathOP<string>(DelegateTarget.StringTarget);
- oString("I'm String Delegate");
- MathOP<int> oInt = new MathOP<int>(DelegateTarget.IntTarget);
- oInt(9);
- Console.ReadLine();
- }
- }
- }
运行结果就不贴出来了,大家应该懂的了。我还想说一点是其实使用泛型来创建一个泛型委托之外用object也是可以的,public deleate void MathOP(object obj);使用方法就是一样的了,但是这样会增加装箱跟拆箱的操作,而使用泛型委托,我们就可以避免这些问题并且获得相同的灵活性。
委托到这里就差不多说完了,委托的基情好朋友--事件,我可能会在以后的一个未知的时间写上两篇。其实写这两篇委托,目的是为了接下去写的线程做一个知识的准备,所以接下去的N篇博文都是会与大家一起探讨一下线程,有兴趣的朋友可以继续关注。谢谢。