委托是用来处理其他语言使用函数指针解决问题时的对应办法。不过不同于C++的函数指针,委托是完全面对对象的;另外C++指针仅指向成员函数,而委托同时封装了对象实例和方法。
委托定义了一个从System.Delegate类派生的类。委托实例封装一个调用列表,该列表列出一个或多个方法,其中每个方法均作为一个可调用实体来引用。对于实例方法,可调用实体由该方法和一个相关联的实例组成;对于静态方法,可调用实体仅由一个方法组成。用一个适当的参数集来调用一个委托实例,就是用此给定的参数集来调用该委托实例的每个可调用实体。
委托实例的特别的属性:它不关心所封装的方法所属的类,只在乎这些方法必须和委托的类型兼容。
15.1委托声明
new修饰符仅允许出现在其他类型中声明的委托上使用,表示所声明的委托会隐藏具有相同名称的继承成员。
当方法和委托有相同的返回类型,且具有相同的参数数目,参数类型、顺序和修饰符相同,那么就说方法和委托类型是兼容的。
C#中方的委托类型是名称等效,而不是结构等效。意思就是两个委托类型,即使具有相同的参数列表和返回类型,仍被认为是不同的两个委托类型;不过像这样彼此不同但结构上相同的委托类型,它们实例在比较时可以认为是相等关系。比如:
其中,D1和D2都与A.M1和B.M1兼容,但D1和D2是两个不同的类型。D1和D2与类B的M2、M3和M4不兼容,因为返回类型或参数列表不同。
委托类型是从System.Delegate派生出来的类类型,隐含sealed,所以不允许从委托类型派生任何类型。System.Delegate本身同样不是委托类型,而是类类型。
C#设置了专门的语法用于委托类型的实例化和调用,除实例化外,所有可以应用于类或类实例的操作也可以应用于委托类或委托实例。
委托实例所封装的方法合成调用列表。从某个方法创建一个委托实例时,该委托实例将封装此方法,此时它的调用只包含一个进入点。但当组合两个非空委托实例时,它们的调用表被连接在一起,按左操作数优先,然后右操作数在后的顺序组成一个新的调用列表,它此时就包含了两个或更多的进入点。
委托还可以使用+、+=、-、-=来组合。比如:
15.2委托实例化
委托的实例由委托创建表达式创建,因此新创建的委托实例将引用一下各项之一:1.委托创建表达式中引用的静态方法;
2.委托创建表达式中引用的目标对象(不能为null)和实例方法;
3.另一个委托。
委托实例一旦被实例化后,将始终引用同一个目标对象和方法。当组合两个委托或从组合委托中移除一个委托时,会产生新的委托,新委托由自己的调用列表,组合委托保持不变。
15.3委托调用
C#为调用委托提供了专门的语法:
当调用非空的、调用列表仅包含一个进入点的委托实例时,它调用调用列表中的方法,委托调用所使用的参数和返回的值均与该方法的对应项相同。如果在对这样的委托进行调用期间发生异常,而且没有在被调用的方法内捕捉到该异常,则会在调用该委托的方法内继续搜索与该异常对应的catch子句,就像调用该委托的方法直接调用了该委托所引用的方法一样;
当调用列表包含多个进入点的委托实例时,那么调用委托实例就是按顺序同步地调用各个方法。以这种方式调用的每个方法都使用相同的参数集。如果这样的委托调用包含引用参数,那么每个方法调用都将使用同一个变量的引用;若期间某个方法对该变量进行更改,后面的方法都会见到此变更;如果委托调用包含输出参数或返回值,则它们的最终只就是最后一个方法调用所产生的结果。
试图调用值为null的委托实例将导致System.NullReferenceException类型的异常。
当移除委托时,实际上移除的是调用列表最后出现的那个委托;当从调用列表移除表中没有的委托时,不算错误。最后的结果如图: