7.3.3 多播委托
前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。委托也可以包含多个方法。这种委托称为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果。
下面的代码取自于SimpleDelegate示例。尽管其语法与以前相同,但实际上它实例化了一个多播委托Operations:
delegate void DoubleOp(double value); // delegate double DoubleOp(double value); // can't do this nowclass MainEntryPoint { static void Main() { DoubleOp operations = new DoubleOp(MathOperations.MultiplyByTwo); operations += new DoubleOp(MathOperations.Square); |
使用委托推断可以编写上面的代码。另外,这种方式也更容易理解:
DoubleOp operations = MathOperations.MultiplyByTwo; operations += MathOperations.Square; |
在前面的示例中,要存储对两个方法的引用,所以实例化了一个委托数组。而这里只是在一个多播委托中添加两个操作。多播委托可以识别运算符+和+=。还可以扩展上述代码中的最后两行,它们具有相同的效果:
DoubleOp operation1 = MathOperations.MultiplyByTwo; DoubleOp operation2 = MathOperations.Square; DoubleOp operations = operation1 + operation2; |
多播委托还识别运算符–和–=,以从委托中删除方法调用。
注意:
根据后面的内容,多播委托是一个派生于System.MulticastDelegate的类,System.MulticastDelegate又派生于基类System.Delegate。System.MulticastDelegate的其他成员允许把多个方法调用链接在一起,成为一个列表。
为了说明多播委托的用法,下面把SimpleDelegate示例改写为一个新示例MulticastDelegate。现在需要把委托表示为返回void的方法,就应重写MathOperations类中的方法,让它们显示其结果,而不是返回它们:
class MathOperations { public static void MultiplyByTwo(double value) { double result = value*2; Console.WriteLine( "Multiplying by 2: {0} gives {1}", value, result); }public static void Square(double value) { double result = value*value; Console.WriteLine("Squaring: {0} gives {1}", value, result); } } 为了适应这个改变,也必须重写ProcessAndDisplayNumber: static void ProcessAndDisplayNumber(DoubleOp action, double valueToProcess) { Console.WriteLine("\nProcessAndDisplayNumber called with value = " + valueToProcess); action(valueToProcess); } |
下面测试多播委托,其代码如下:
static void Main() { DoubleOp operations = MathOperations.MultiplyByTwo; operations += MathOperations.Square;ProcessAndDisplayNumber(operations, 2.0); ProcessAndDisplayNumber(operations, 7.94); ProcessAndDisplayNumber(operations, 1.414); Console.WriteLine(); } |
现在,每次调用ProcessAndDisplayNumber时,都会显示一个信息,说明它已经被调用。然后,下面的语句会按顺序调用action委托实例中的每个方法:
action(value);
运行这段代码,得到如下所示的结果:
MulticastDelegateProcessAndDisplayNumber called with value = 2 Multiplying by 2: 2 gives 4 Squaring: 2 gives 4 ProcessAndDisplayNumber called with value = 7.94 Multiplying by 2: 7.94 gives 15.88 Squaring: 7.94 gives 63.0436 ProcessAndDisplayNumber called with value = 1.414 Multiplying by 2: 1.414 gives 2.828 Squaring: 1.414 gives 1.999396 |
如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,因此应避免编写依赖于以特定顺序调用方法的代码。
通过一个委托调用多个方法还有一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。下面是MulticastIteration示例。其中定义了一个简单的委托DemoDelegate,它没有参数,返回void。这个委托调用方法One()和Two(),这两个方法满足委托的参数和返回类型要求。注意方法One()抛出了一个异常:
using System; namespace Wrox.ProCSharp.Delegates { public delegate void DemoDelegate(); class Program { static void One() { Console.WriteLine("One"); throw new Exception("Error in one"); } static void Two() { Console.WriteLine("Two"); } |
在Main()方法中,创建了委托d1,它引用方法One(),接着把Two()方法的地址添加到同一个委托中。调用d1委托,就可以调用这两个方法。异常在try/catch块中捕获:
static void Main() { DemoDelegate d1 = One; d1 += Two; try { d1(); } catch (Exception) { Console.WriteLine("Exception caught"); } } } } |
委托只调用了第一个方法。第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。当调用方法的顺序没有指定时,结果会有所不同。
注意:
错误和异常详见第13章。
在这种情况下,为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。
static void Main() { DemoDelegate d1 = One; d1 += Two; Delegate[] delegates = d1.GetInvocationList(); foreach (DemoDelegate d in delegates) { try { d(); } catch (Exception) { Console.WriteLine("Exception caught"); } } } |
修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。
【责任编辑:
雪花 TEL:(010)68476606】