委托 实际很简单(上)
什么是委托?
MSDN中给出的答案是“委托是一种引用方法的类型。”
看到这个答案,相信很多人都会觉得不知所云,能够理解的人,您一定已经对委托很熟悉了,所以后面的内容也许并不适合您。
实际上,从这个答案中,我们只需要明白两个问题就可以了,第一个问题就是“委托是一种类型”,既然委托是一种类型,那么,从直观上看,委托类型和.Net中的其它类型,如string、DateTime,都一样,都是可以用来声明具体的类型实例的。第二个问题就是“引用方法的类型”,这句话就告诉了我们,象string可以声明一个字符串对象,DateTime可以声明一个时间对象一样,委托可以声明一个引用方法的对象。
什么是引用方法的对象?学过C或C++的人都知道函数的指针,这个指针可以指向一个具体函数,没错,使用了委托定义出来的对象就象一个函数的指针,但它可以做更多的事情。呃...有人扔砖头了,记住啊,我从来没说过委托就是函数的指针,我只是打了个比方哦。
你不知道什么是C或C++?你哪里来的?火星...好了,怕了你了。如果你没听说过函数的指针,那么你总知道什么是函数或者方法吧?OK,委托类型可以声明一个对象,这个对象可以和一个函数或方法进行关联,然后你就可以用这个委托对象去操作你关联的函数或方法了。明白了吗?你问我为什么要用委托去操作关联的函数或方法而不是直接去调用它们?呃...Good Question! 请带着你的疑问往下看吧,后面我要告诉大家什么地方能够用到委托以及如何去用。如果你能看明白了,那么我想你自己一定就会得到这个问题的答案了。
委托用途之一:函数(方法)调用
既然委托是跟方法有关的类型,我们就先来看看如何来使用委托进行方法的调用。先来定义一个委托类型:
1: public delegate int TestDelegate(int i1, int i2);
定义委托类型使用delegate关键字,就像定义其它的类型使用class关键字一样。如果我们忽略掉delegate关键字,那么这个定义看上去就像声明了一个函数。的确,定义一个委托类型,除了要指定它的名字外,就是要说明这个委托类型所能调用的函数是什么样子的(都有哪些参数,返回值是什么类型)。如果你忘记了委托定义的格式,就想想函数是如何定义的吧。很多文章中都提到了,委托类型要与它想要调用的函数具有相同的签名,这个相同的签名,指的就是相同的参数类型和返回类型。
我们声明的这个TestDelegate委托类型就是用来调用这样的函数--具有两个int参数并且返回类型为int类型。
定义了委托类型之后,我们如何使用它呢?委托类型的使用和其它的类型一样,需要实例化一个具体的委托对象。就象这样:
1: public delegate int TestDelegate(int i1, int i2);
2:
3: private void button1_Click(object sender, EventArgs e)
4: {
5: TestDelegate TestDeleInstance = new TestDelegate(Add);
6: }
7:
8: private int Add(int i1, int i2)
9: {
10: return i1 + i2;
11: }
看第5行,象实例化其它的类型一样,我们实例化了一个TestDelegate类型TestDeleInstance,在它的构造函数中,需要指定这个委托类型的实例(TestDeleInstance)要调用的是哪个具体的函数。
下面如何通过这个委托的实例调用函数Add呢?
1: private void button1_Click(object sender, EventArgs e)
2: {
3: TestDelegate TestDeleInstance = new TestDelegate(Add);
4: int result;
5: result = TestDeleInstance(1, 2);
6: result = TestDeleInstance.Invoke(1, 2);
7: }
我们可以使用第5行的方法,直接调用这个委托,这使得委托看上去更像一个函数;也可以使用第6行的方法,调用这个委托对象的Invoke方法。无论使用哪种方法,最终都会执行Add这个函数(当然委托还有异步调用的方法,这个会在后面陆续讲解)。可是我们这么折腾了半天,为什么不直接调用Add函数呢?别着急,接着看完下面这段,你可能就会有自己的想法了。
假设我们定义了一个类A
1: public class A
2: {
3: public int OP1;
4: public int OP2;
5: public int GetResult()
6: {
7: return OP1 + OP2;
8: }
9: }
这样,我们可以通过A.GetResult()方法返回A的两个属性OP1和OP2的和。这时,如果我想要A.GetResult()方法返回两个属性的差怎么办?很简单,修改GetReult方法,让它返回OP1 - OP2。如果这个类A是你发布的一个产品,你的用户希望自己控制GetResult方法的逻辑(或许他想返回两个属性的最小值,谁知道呢。),又该怎么办呢?我们看修改后的类A:
1: public class A
2: {
3: public int OP1;
4: public int OP2;
5: public int GetResult(TestDelegate OPDelegate)
6: {
7: return OPDelegate(OP1, OP2);
8: }
9: }
没错,我们给GetResult方法添加了一个参数,一个TestDelegate委托类型的参数。什么?你说我从来没有告诉过你委托可以做参数?拜托,再说一次,委托类型也是类型,跟string一样,其它类型能出现的地方,委托类型也可以。(实际上委托类型比string简单多了,看看string类型有多少成员~~)
现在我们再看看调用A的GetResult方法会发生什么?调用者需要提供一个TestDelegate委托类型的实例,这个实例会指向调用者自己定义好的、一个满足TestDelegate委托类型的签名的方法(这句话有点长,多看两遍啊。签名是什么?往前面看,前面说过。)。也就是说,现在这个GetResult方法,实际上执行的是调用者自己定义的一个函数(方法),那么,调用者想如何控制这个方法的逻辑,就随他去吧。
1: public class B
2: {
3: void testA()
4: {
5: A abc = new A();
6: abc.OP1 = 10;
7: abc.OP2 = 5;
8: int i = abc.GetResult(new TestDelegate(Max));
9: }
10:
11: int Max(int i1, int i2)
12: {
13: return i1 > i2 ? i1 : i2;
14: }
15: }
上面的代码说明,类B调用类A的GetResult方法的时候,指定了由类B自己的Max方法去处理GetResult的逻辑。你看不明白
13: return i1 > i2 ? i1 : i2;
这段代码用一种更简洁的语法表达了这样的一个意思:i1大于i2吗?如果答案是yes,那么请返回i1,否则返回i2吧。
将委托作为参数,给予了类更多的灵活性,也使得调用者有的更多的选择。看到这里,请回想一下当初的问题,函数直接调用和使用委托调用,是不是有些不一样呢?
文章的最后,留一个问题:我们可不可以象下面的代码那样,在一个函数体内定义一个委托类型呢?(这个问题的答案并不重要,重要的是要想明白为什么。)
1: void testA()
2: {
3: delegate void testDele();
4: ...
5: }