第七章:委托和事件--委托(c#高级编程 第6版)

委托概述:

  1. .net以委托的方式实现了函数指针的概念,与C语言不同,.net的委托是类型安全的。
  2. 当把方法传送给其他方法时,需要使用委托。
  3. 关于委托的类型安全:委托封装了方法的细节,定义委托时必须给出它所代表的方法签名和返回类型等全部细节。

1. 声明委托

  • 定义委托,就是告诉编译器这种类型的委托代表了那种类型的方法,然后创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。
  • 理解委托的一种好方式是把委托当做给方法签名返回类型指定名称,定义委托时必须给出它所代表的方法签名和返回类型等全部细节。
  • 定义一个委托,基本上是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在另一个类的内部定义,也可以在任何类的外部定义。
  • 委托的声明例子:
    delegate double DoubleOp(double x);
    表示该委托代表一个接受double参数,返回一个double值的方法。

2. 使用委托

  • 委托在语法上总是带有一个参数的构造函数,这个参数就是委托引用的方法:这个方法必须匹配最初定义委托时的签名。
  • 调用委托时,必须提供委托实例的名称,后面的括号中应包含调用委托中的方法时使用的参数。
  • 给委托实例提供括号与使用该实例的Invoke方法完全相同。
  • 委托推断:为减少输入,也可以直接将方法的地址赋给委托实例,编译器可以把委托实例解析为特定的类型。
  • 调用时只需要被调用的方法签名是正确的,并不考虑调用此方法的是什么类型,也不管这个方法静态与否。
  • 委托的使用例子:
    class Program
    {
    //委托的定义
    private delegate string GetAString();


    static void Main()
    {
    int x = 40;
    // 委托的实例化
    GetAString firstStringMethod = new GetAString(x.ToString);
    //或者使用如下委托推断实例化
    // GetAString firstStringMethod = x.ToString;
    // 委托的调用
    Console.WriteLine("String is " + firstStringMethod());
    // 或者使用Invoke方法调用
    Console.WriteLine("String is " + firstStringMethod.Invoke());
    // 以上的委托调用等同于下面一句:
    Console.WriteLine("String is" + x.ToString());
    }

 3. 使用委托的两个例子:

  • 委托数组
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace Wrox.ProCSharp.Delegates
    {
    class MathOperations
    {
    public static double MultiplyByTwo(double value)
    {
    return value * 2;
    }

    public static double Square(double value)
    {
    return value * value;
    }
    }


    delegate double DoubleOp(double x);

    class MainEntryPoint
    {
    static void Main()
    {
    // 定义委托数组
    DoubleOp[] operations =
    {
    MathOperations.MultiplyByTwo,
    MathOperations.Square
    //new DoubleOp(MathOperations.MultiplyByTwo),
    //new DoubleOp(MathOperations.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);
    }
    }
    }
  • 为什么要用委托?另一个例子
    例如,如何写一个能给任何对象排序的冒泡排序函数Sort()?由于不知道如何具体比较两个对象的大小,这样实现起来很困难。
    一个解决办法是:给某一类型的对象数组排序时,将比较这一类型两个对象大小的方法也传给Sort函数,通过委托来实现。
    1. 排序函数的实现:
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace Wrox.ProCSharp.Delegates
    {
    // 定义委托
    delegate bool Comparison(object x, object y);

    class BubbleSorter
    {
    // 接受特定类型的比较方法做为参数
    static public void Sort(object[] sortArray, Comparison comparer)
    {
    for (int i = 0; i < sortArray.Length; i++)
    {
    for (int j = i + 1; j < sortArray.Length; j++)
    {
    // 使用特定类型的方法进行比较
    if (comparer(sortArray[j], sortArray[i]))
    {
    object temp = sortArray[i];
    sortArray[i]
    = sortArray[j];
    sortArray[j]
    = temp;
    }
    }
    }
    }
    }
    }
    2. 要比较的类型定义
    View Code
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace Wrox.ProCSharp.Delegates
    {
    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("{0}, {1:C}", name, salary);
    }

    // 定义该类型的比较方法
    public static bool CompareSalary(object x, object y)
    {
    Employee e1
    = (Employee)x;
    Employee e2
    = (Employee)y;
    return (e1.salary < e2.salary);
    }
    }

    }
    3. 主函数
    View Code
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace Wrox.ProCSharp.Delegates
    {
    class Program
    {
    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)};

    // 传递该类型的比较方法,以用于Sort函数的比较
    BubbleSorter.Sort(employees, Employee.CompareSalary);

    foreach (var employee in employees)
    {
    Console.WriteLine(employee);
    }
    //for (int i = 0; i < employees.Length; i++)
    // Console.WriteLine(employees[i].ToString());
    }
    }
    }

4. 多播委托

  1. 什么是多播委托,如何使用多播委托
    • 一个委托可以包含多个方法,这种委托称之为多播委托(MulticastDelegate)
    • 如果调用多播委托,就会按顺序连续调用多个方法
    • 因此,委托的签名就必须返回void,否则就只能得到委托调用的最后一个方法的结果
    • 多播委托可以识别+、+=、—、—=,以添加或删除方法
      // 可以这样使用多播委托
      DoubleOp operations = MathOperations.MultiplyByTwo;
      operations
      += MathOperations.Square;

      // 也可以这样使用多播委托
      DoubleOp operation1 = MathOperations.MultiplyByTwo;
      DoubleOp operation2
      = MathOperations.Square;
      DoubleOp MulticastDelegate
      = operation1 + operation2;
  2. 一个使用多播委托的实例
    MulticastDelegate
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace MulticastDelegate
    {
    delegate void DoubleOp(double value);

    class MathOperations
    {
    // 方法1
    public static void MultiplyByTwo(double value)
    {
    double result = value * 2;
    Console.WriteLine(
    "Multiplying by 2: {0} gives {1}", value, result);
    }

    // 方法2
    public static void Square(double value)
    {
    double result = value * value;
    Console.WriteLine(
    "Squaring: {0} gives {1}", value, result);
    }
    }

    class Program
    {
    // 接收多播委托的方法
    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();
    }
    }
    }
  3. 手动迭代多播委托中的方法列表
    • 对同一个多播委托调用方法链的顺序并未正式定义,因此应该尽量避免编写依赖于以特定顺序调用方法的代码
    • 多播委托包含一个逐个调用的委托集合,如果通过委托调用的一个方法跑出了异常,整个迭代就会停止
    • 为了避免这个问题,可以通过Delegate类的GetInvocationList()方法获取Delegate对象数组,手动迭代方法列表
      View Code
      using System;

      namespace Wrox.ProCSharp.Delegates
      {
      class Program
      {
      static void One()
      {
      Console.WriteLine(
      "One");

      // 抛出异常
      throw new Exception("Error in one");
      }

      static void Two()
      {
      Console.WriteLine(
      "Two");
      }

      public delegate void DemoDelegate();

      static void Main()
      {
      // 多播委托
      DemoDelegate d1 = One;
      d1
      += Two;

      // 获取 DelegateList
      Delegate[] delegates = d1.GetInvocationList();

      // 手动迭代各个Delegate
      foreach (DemoDelegate d in delegates)
      {
      try
      {
      d();
      }
      catch (Exception)
      {
      Console.WriteLine(
      "Exception caught");
      }
      }
      }
      }
      }

5. 匿名方法

  • 一个例子
    匿名方法例子
    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"));
    }
    }
    }
  • 使用匿名方法注意事项:
    1. 必须遵循两个原则
      (1)在匿名方法中不能使用跳转语句跳到该匿名方法的外部
      (2)反之亦然,外部跳转语句不能跳转到匿名方法的内部
    2. 匿名方法内部不能访问不安全的代码
    3. 匿名方法不能访问在该方法外部定义的ref和out参数,但可以使用在匿名方法外部定义的其他变量
    4. 如果需要匿名方法多次编写同一个功能,就不要使用匿名方法

posted on 2011-04-24 21:52  I过T  阅读(696)  评论(1编辑  收藏  举报

导航