委托

  委托(delegate)是一种存储函数引用的类型。这听起来相当高深,但其机制是非常简单的。委托最重要的用途在本书后面介绍到事件和事件处理时才能解释清楚,但这里也将介绍有关委托的许多内容。委托的声明非常类似于函数,但不带函数体,且要使用 delegate 关键字。委托的声明指定了一个返回类型和一个参数列表。

  定义了委托后,就可以声明该委托类型的变量。接着把这个变量初始化为与委托具有相同返回类型和参数列表的函数引用。之后,就可以使用委托变量调用这个函数,就像该变量是一个函数一样。

  有了引用函数的变量后,就可以执行不能用其他方式完成的操作。例如,可以把委托变量作为参数传递给一个函数,这样,该函数就可以使用委托调用它引用的任何函数,而且在运行之前不必知道调用的是哪个函数。下面的示例使用委托访问两个函数中的一个。

  把下列代码添加到 Program.cs 中:

        class Program
        {
            delegate double ProcessDelegate(double param1, double param2);

            static double Multiply(doule param1, double param2)
            {
                return param1 * param2;
            }

            static double Divide(double param1, double param2)
            {
                return param1 /  param2;
            }

            static void Main(string[] args)
            {
                ProcessDelegate process;
                Console.WriteLine("Enter 2 numbers separated with a comma:");
                string input = Console.ReadLine();
                int commaPos = input.IndexOf( ',' );
                double param1 = Convert.ToDouble(input.Substring(0, commaPos));
                double param2 = Convert.ToDouble(intput.Substring(commaPos + 1, input.Length - commaPos - 1));
                Console.WriteLine("Enter M to multiply or D to divide:");
                input = Console.ReadLine();
                if (input == 'M')
                    process = new ProcessDelegate(Multiply);
                else
                    process = new ProcessDelegate(Divide);
                Console.WriteLine("Result: {0}", process(param1, param2));
                Console.ReadKey();
            }
        }

  示例的说明

  这段代码定义了一个委托 ProcessDelegate,其返回类型和参数与函数 Multiply()Divide() 相匹配。委托的定义如下所示:

        delegate double ProcessDelegate(double param1, double param2);

  delegate 关键字指定该定义是用于委托的,而不是用于函数的(该定义所在的位置与函数定义相同)。接着,该定义指定 double 返回类型和两个 double 参数。实际使用的名称可以是任意的,所以可以给委托类型和参数指定任意名称。这里委托名是 ProcessDelegatedouble 参数名是 param1param2

  Main() 中的代码首先使用新的委托类型声明一个变量:

        static void Main(string[] args)
        {
            ProcessDelegate process;
        }

  接着用一些比较标准的C#代码请求由逗号分隔的两个数字,并将这些数字放在两个 double 变量中:

        Console.WriteLine("Enter 2 numbers separated with a comma:");
        string input = Console.ReadLine();
        int commaPos = input.IndexOf(',');
        double param1 = Convert.ToDouble(input.Substring(0, commaPos));
        double param2 = Convert.ToDouble(input.Substring(commaPos + 1, input.Length.Length - commaPos - 1));

  为说明问题,这里没有验证用户输入的有效性。如果这些是 “现实中的”代码,就应花费更多的时间来确保在局部变量 param1param2 中得到有效的值。

  接着,询问用户是要相乘,还是相除这两个数字:

        Console.WriteLine("Enter M to multiply or D to divide:");
        intput = Console.ReadLine();

  根据用户的选择,初始化 process 委托变量:

        if (input == "M")
            process = new ProcessDelegate(Multiply);
        else
            process = new ProcessDelegate(Divide);

  要把一个函数引用赋给委托变量,需要使用略显古怪的语法。这个过程比较类似于给数组赋值,必须使用 new 关键字创建一个新委托。在这个关键字的后面,指定委托类型,提供一个引用所需函数的参数,这里也就是 Multiply()Divide() 函数。注意 ⚠️这个参数与委托类型或目标函数的参数不匹配,这是委托赋值的一个独特语法,参数是要使用的函数名,且不带括号。

  实际上,这里可以使用略微简单的语法:

        if (input == "M")
            process = Multiply;
        else
            process = Divide;

  编译器会发现,process 变量的委托类型匹配两个函数的签名,于是自动初始化一个委托。可以自行确定使用哪个语法,但一些人喜欢使用较长的版本,因为它更容易一眼看出会发生什么。

  最后,使用该委托调用所选的函数。无论委托引用的是什么函数,该语法都是有效的:

        Console.WriteLine("Result: {0}", process(param1, param2));
        Console.ReadKey();

  这里把委托变量看作一个函数名。但与函数不同,我们还可以对这个变量执行更多操作,例如,通过参数将其传递给一个函数,如下例所示:

        static void ExecuteFunction(ProcessDelegate process)
        {
            process(2.2, 3.3);
        }

  就像选择一个要使用的 “插件”一样,通过把函数委托传递给函数,就可以控制函数的执行。例如,一个函数要对字符串数组按照字母进行排序。对列表排序有几个不同的方法,它们的性能取决于要排序的列表特性。使用委托可以把一个排序算法函数委托传递给排序函数,指定要使用的方法。

  委托有许多用途,但如前所述,它们的大多数常见用途主要与事件处理有关,具体内容详见 第 13 章。

🔚