背景:                 
[本书目录] [图书首页] [本书讨论区]  
链接地址:http://www.17xie.com/read-58505.html    注册17xie 一起来写书 实现您的出书梦想!

7.3  匿名方法

到目前为止,要想使委托工作,方法必须已经存在(即委托是用方法的签名定义的)。但使用委托还有另外一种方式:即通过匿名方法。匿名方法是用作委托参数的一个代码块。

用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

using System;

namespace Wrox.ProCSharp.Delegates

{

  class Program

  {

    delegate string DelegateTest(string val);

    static void Main()

    {

      string mid = ", middle part,";

      delegateTest anonDel = delegate(string param)

      {

        param += mid;

        param += " and this was added to the string.";

        return param;

      };

      Console.WriteLine(anonDel("Start of string"));

    }

  }

}

委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

delegate(string param)

{

  param += mid;

  param += " and this was added to the string.";

  return param;

};

可以看出,该代码块使用方法级的字符串变量mid,该变量是在匿名方法的外部定义的,并添加到要传送的参数中。接着代码返回该字符串值。在调用委托时,把一个字符串传送为参数,将返回的字符串输出到控制台上。

匿名方法的优点是减少了要编写的代码。方法仅在由委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

在使用匿名方法时,必须遵循两个规则。在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。

如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

7.3.1  简单的委托示例

在这个示例中,定义一个类MathsOperations,它有两个静态方法,对double类型的值执行两个操作,然后使用该委托调用这些方法。这个数学类如下所示:

   class MathsOperations

   {

      public static double MultiplyByTwo(double value)

      {

         return value*2;

      }

      public static double Square(double value)

      {

         return value*value;

      }

   }

下面调用这些方法:

using System;

namespace Wrox.ProCSharp.Delegates

{

   delegate double DoubleOp(double x);

   class MainEntryPoint

   {

      static void Main()

      {

         DoubleOp [] operations =

            {

               new DoubleOp(MathsOperations.MultiplyByTwo),

               new DoubleOp(MathsOperations.Square)

            };

         for (int i=0 ; i<operations.Length ; i++)

         {

            Console.WriteLine("Using operations[{0}]:", i);

            ProcessAndDisplayNumber(operations[i], 2.0);

            ProcessAndDisplayNumber(operations[i], 7.94);

            ProcessAndDisplayNumber(operations[i], 1.414);

            Console.WriteLine();

         }

      }

      static void ProcessAndDisplayNumber(DoubleOp action, double value)

      {

         double result = action(value);

         Console.WriteLine(

            "Value is {0}, result of operation is {1}", value, result);

}

}

}

在这段代码中,实例化了一个委托数组DoubleOp (记住,一旦定义了委托类,就可以实例化它的实例,就像处理一般的类那样—— 所以把一些委托的实例放在数组中是可以的)。该数组的每个元素都初始化为由MathsOperations类执行的不同操作。然后循环这个数组,把每个操作应用到3个不同的值上。这说明了使用委托的一种方式—— 把方法组合到一个数组中,这样就可以在循环中调用不同的方法了。

这段代码的关键一行是把委托传递给ProcessAndDisplayNumber()方法,例如:

            ProcessAndDisplayNumber(operations[i], 2.0);

其中传递了委托名,但不带任何参数,假定operations[i]是一个委托,其语法是:

●       operations[i]表示“这个委托”。换言之,就是委托代表的方法。

●       operations[i](2.0)表示“调用这个方法,参数放在括号中”。

ProcessAndDisplayNumber()方法定义为把一个委托作为其第一个参数:

      static void ProcessAndDisplayNumber(DoubleOp action, double value)

在这个方法中,调用:

         double result = action(value);

这实际上是调用action委托实例封装的方法,其返回结果存储在result中。

运行这个示例,得到如下所示的结果:

SimpleDelegate

Using operations[0]:

Value is 2, result of operation is 4

Value is 7.94, result of operation is 15.88

Value is 1.414, result of operation is 2.828

Using operations[1]:

Value is 2, result of operation is 4

Value is 7.94, result of operation is 63.0436

Value is 1.414, result of operation is 1.999396

如果在这个例子中使用匿名方法,就可以删除第一个类MathOperations。Main方法应如下所示:

static void Main()

{

  DoubleOp multByTwo = delegate(double val) {return val * 2;}

  DoubleOp square = delegate(double val) {return val * val;}

  DoubleOp [] operations = {multByTwo, square};

  for (int i=0 ; i<operations.Length ; i++)

  {

    Console.WriteLine("Using operations[{0}]:", i);

    ProcessAndDisplayNumber(operations[i], 2.0);

    ProcessAndDisplayNumber(operations[i], 7.94);

    ProcessAndDisplayNumber(operations[i], 1.414);

    Console.WriteLine();

  }

}

运行这个版本,结果与前面的例子相同。其优点是删除了一个类。

7.3.2  BubbleSorter示例

下面的示例将说明委托的用途。我们要编写一个类BubbleSorter,它执行一个静态方法Sort(),这个方法的第一个参数是一个对象数组,把该数组按照升序重新排列。换言之,假定传递的是int数组:{0, 5, 6, 2, 1},则返回的结果应是{0, 1, 2, 5, 6}。

冒泡排序算法非常著名,是一种排序的简单方法。它适合于一小组数字,因为对于大量的数字(超过10个),还有更高效的算法。冒泡排序算法重复遍历数组,比较每一对数字,按照需要交换它们的位置,把最大的数字逐步移动到数组的最后。对于给int排序,进行冒泡排序的方法如下所示:

   for (int i = 0; i < sortArray.Length; i++)

   {

      for (int j = i + 1; j < sortArray.Length; j++)

      {

         if (sortArray[j] < sortArray[i])   // problem with this test

         {

            int temp = sortArray[i];   // swap ith and jth entries

            sortArray[i] = sortArray[j];

            sortArray[j] = temp;

         }

      }

   }

它非常适合于int,但我们希望Sort()方法能给任何对象排序。换言之,如果某段客户机代码包含Currency结构数组或其他类和结构,就需要对该数组排序。这样,上面代码中的if(sortArray[j] < sortArray[i])就有问题了,因为它需要比较数组中的两个对象,看看哪一个更大。可以对int进行这样的比较,但如何对直到运行期间才知道或确定的新类进行比较?答案是客户机代码知道类在委托中传递的是什么方法,封装这个方法就可以进行比较。

定义如下的委托:

   delegate bool CompareOp(object lhs, object rhs);

给Sort方法指定下述签名:

   static public void Sort(object [] sortArray, CompareOp gtMethod)

这个方法的文档说明强调,gtMethod必须表示一个静态方法,该方法带有两个参数,如果第二个参数的值“大于”第一个参数(换言之,它应放在数组中靠后的位置),就返回true。

注意:

这里使用的是委托,但也可以使用接口来解决这个问题。.NET提供的IComparer接口就用于此目的。但是这里使用委托是因为这种问题本身要求使用委托。

设置完毕后,下面定义类BubbleSorter:

   class BubbleSorter

   {

      static public void Sort(object [] sortArray, CompareOp gtMethod)

      {

         for (int i=0 ; i<sortArray.Length ; i++)

         {

            for (int j=i+1 ; j<sortArray.Length ; j++)

            {

               if (gtMethod(sortArray[j], sortArray[i]))

               {

                  object temp = sortArray[i];

                  sortArray[i] = sortArray[j];

                  sortArray[j] = temp;

               }

            }

         }

      }

   }

为了使用这个类,需要定义另一个类,建立要排序的数组。在本例中,假定Mortimer Phones移动电话公司有一个员工列表,要对照他们的薪水进行排序。每个员工分别由类Employee的一个实例表示,如下所示:

   class Employee

   {

      private string name;

      private decimal salary;

      public Employee(string name, decimal salary)

      {

         this.name = name;

         this.salary = salary;

      }

      public override string ToString()

      {

         return string.Format(name + ", {0:C}", salary);

      }

      public static bool RhsIsGreater(object lhs, object rhs)

      {

         Employee empLhs = (Employee) lhs;

         Employee empRhs = (Employee) rhs;

         return (empRhs.salary > empLhs.salary);

      }

   }

注意,为了匹配CompareOp委托的签名,在这个类中必须定义RhsIsGreater,它的参数是两个对象引用,而不是Employee引用。必须把这些参数的数据类型转换为Employee引用,才能进行比较。

下面编写一些客户机代码,完成排序:

using System;

namespace Wrox.ProCSharp.Delegates

{

   delegate bool CompareOp(object lhs, object rhs);

   class MainEntryPoint

   {

      static void Main()

      {

         Employee [] employees =

            {

               new Employee("Bugs Bunny", 20000),

               new Employee("Elmer Fudd ", 10000),

               new Employee("Daffy Duck", 25000),

               new Employee("Wiley Coyote", (decimal)1000000.38),

               new Employee("Foghorn Leghorn", 23000),

               new Employee("RoadRunner'", 50000)};

         CompareOp employeeCompareOp = new CompareOp(Employee.RhsIsGreater);

         BubbleSorter.Sort(employees, employeeCompareOp);

         for (int i=0 ; i<employees.Length ; i++)

         {

            Console.WriteLine(employees[i].ToString());

}

      }

     }

   }

运行这段代码,正确显示按照薪水排列的Employee,如下所示:

BubbleSorter

Elmer Fudd, $10,000.00

Bugs Bunny, $20,000.00

Foghorn Leghorn, $23,000.00

Daffy Duck, $25,000.00

RoadRunner, $50,000.00

Wiley Coyote, $1,000,000.38

7.3.3  多播委托

前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。委托也可以包含多个方法。这种委托称为多播委托。如果调用多播委托,就可以按顺序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果。

下面的代码取自于SimpleDelegate示例。尽管其语法与以前相同,但实际上它实例化了一个多播委托Operations:

   delegate void DoubleOp(double value);

//   delegate double DoubleOp(double value);   // can't do this now

   class 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);

运行这段代码,得到如下所示的结果:

MulticastDelegate

ProcessAndDisplayNumber 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()方法。当调用方法的顺序没有指定时,结果会有所不同。

One

Exception Caught

注意:

错误和异常详见第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");

}

}

}

修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

One

Exception caught

Two


字数:17387    最后更新:7个月以前 [04-10 21:25]happyskynet 修改
本页编辑者:happyskynet  
[前一页]:7.2 委托推断  [后一页]:7.4 事件
[在本页中加入书签] [收藏本书] [推荐本书]
  17xie论坛 > 本书讨论区 > 本页评论   (共0条)
发表评论

用户名称 匿名发表
评论内容
验证码

关于我们 | 版权声明 | 免责声明 | 诚聘英才 | 联系我们 | 合作伙伴 | 友情链接 | 广告合作 | 提交意见
Copyright © 2007 17xie.com 互联网协同写书平台 京ICP备08002671号