C#中的委托定义与用法

开发工具与关键技术:Visual Studio 2017

作者:

撰写时间:2019 年 7 月 3 日

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

当要把方法传送给其他方法时,就要使用委托。可以看看下面一行代码:

int i = int.Parse("99");

平时我们都习惯于把数据作为参数传给方法,如上面的例子所示。所以,给方法传递另一个方法听起来优点奇怪。而有时候某个方法执行的操作并不是针对数据进行的,而是要对另一个方法进行调用。更麻烦的是,在编译时我们不知道第二个方法是什么,这个信息只能在运行时得到,所以需要把第二个方法作为参数传递给第一个方法。下面用几个例子说明:

  1. 启动线程和任务:

在C#中,可以告诉计算机并运行某些新的执行序列,同时运行当前的任务。这种序列就称为线程,在一个基类System.Threading.Thread的实例上使用方法Start(),就可以启动一个线程。如果要诉计算机要启动一个新的执行序列,就必须说明要在哪里启动该序列;必须为计算机提供开始启动的方法的细节,即Thread类的构造函数必须带有一个参数,该参数定义了线程调用的方法。

  1. 通用库类:

许多库包含执行各种标准任务得代码。这些库通常可以自我包含,这样在编写库时,就会知道任务该如何执行。但是有时候在任务中还包含子任务,只有使用该库得客户端代码才知道如何执行任务。例如,假设要编写一个类,它带有一个对象数组,并把它们按升序排列。但是排序得部分过程会涉及重复使用数组中得两个对象,比较它们,看看哪一个应放在前面。如果要编写得类必须能对任何对象数组排序,就无法提前告诉计算机应如何比较对象。处理类中的对象数组的客户端代码也必须告诉类如何比较要排序的特定对象。总之,客户端代码必须给类传递某个可以调用并进行这种比较的合适方法的细节。

  1. 事件:

一般的思路是通知代码发生了什么事件。GUI编程主要处理事件。在引发事件时,运行库需要知道应该执行那个方法。这就需要把处理事件的方法作为一个参数传递给委托。

  声明委托:

  在C#中使用一个类时,分两个阶段操作。首先,需要定义这个类,即告诉编译器这个类由什么字段和方法组成。然后(排除只是用静态方法),实例化该类的一个对象。使用委托时,也需要经过这两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法。然后,必须创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。

   声明委托的语法如下:

delegate void IntMethodInvoker(int x);

在这个示例中,声明了一个委托IntMethodInvoker,并指定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它表示的方法的签名和返回类型等全部细节(理解委托的一种好方式是把委托视为给方法的签名和返回类型指定名称)。

假定要定义一个委托TwoLongsOp,该委托表示的方法有两个long型参数,返回类型为double。

代码如下:

      delegate double TwoLongsOp(long first, long second);

 或者要定义一个委托,它表示的方法不带参数,返回一个string型的值,可以编写如下代码:

delegate string GetAString();

其语法类似于方法的定义,但没有方法主体,且定义的前面要加上关键字delegate。因为定义委托基本上是定义一个新类,所以可以在定义类的任何相同地方定义委托。也就是说,可以在另一个类的内部定义委托,也可以在任何类的外部定义委托,还可以在名称空间中把委托定义为顶层对象。根据定义的可见性和委托的作用域,可以在委托,可以在委托的定义上应用任意常见的访问修饰符:public、private、protected等:

  

注意:实际上,“定义一个委托”是指“定义一个新类”。委托实现为派生自基类System.MulticastDelegate的类,System.MulticastDelegate又派生自基类System.Delegate。C#编译器能识别这个类,会使用其委托语法,因此我们不需要了解这个类的具体执行情况。

定义好委托后,就可以创建它的一个实例,从而用该实例存储特定方法的细节。注意:但是,此处在术语方面有一个问题。类有两个不同的术语:“类”表示较广义的定义,“对象”表示类的实例。但委托只有一个术语。在创建委托的实例时,所创建的委托的实例仍称为委托。必须从上下文中确定所用委托的确切含义。

使用委托:

下面的代码说明了如使用委托。这是在int值上调用ToString()方法的一种相当冗长的方式。

       private delegate string GetAString();

        public static void Main(string[] args)

        {

            int x = 40;

            GetAString firstStringMethod = new GetAString(x.ToString);

            Console.WriteLine($"String is {firstStringMethod()}");

        }

在这段代码中,实例化GetAString的委托,并对它进行初始化,使其引用整型变量x的ToString()方法。在C#中委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。所以在这个示例中,如果用不带参数并返回一个字符串的方法来初始化firstStringMethod变量,就会产生一个编译错误。注意,因为int.String()是一个实例方法(不是静态方法),所以需要指定实例(x)和方法名正确地初始化委托。

下一句代码使用这个委托来显示字符串。在任何代码中,度应该提供委托实例的名称,后面的圆括号应该包含调用该委托中的方法时使用的任何等效参数。所以在上面的代码中。

实际上,给委托实例提供圆括号与调用委托类的Invoke()方法完全相同。因为firstStringMethod是委托类型的一个变量,所以C#编译器会用firstStringMethod.Invoke()代替firstStringMethod()。

为了减少输入量,在需要委托实例的每个位置可以只传送地址的名称。这称为委托推断。只要编译器可以把委托实例解析为特定的类型,这个C#特性就是有效的。下面的示例使用GetAString委托的一个新实例初始化GetAString类型的firstStringMethod变量:

              GetAString firstStringMethod = new GetAString(x.ToString);

只要用变量x把方法名传送给变量firstStringMethod ,就可以编些出相同的代码:

            GetAString firstStringMethod = x.ToString;

C#编译器创建的代码是一样的。由于编译器会用firstStringMethod检测需要的委托类型,因此它创建GetAString委托类型的一个实例,用对象x把方法的地址传送给构造函数。

注意:在调用上述方法名时,输入形式不能为x.ToString()(不要输入圆括号),也不能把它传送给委托变量,输入圆括号会调用一个方法,而调用x.ToString()方法会返回一个不能赋予委托变量的字符串对象。只能把方法的地址赋予给委托变量。

   下面使用GetAString实例,代码如下:

class Program

    {

        private delegate int Currency();

        private delegate string GetAString();

        public static void Main()

        {

            int x = 40;

            GetAString firstStringMethod = x.ToString;

            Console.WriteLine($"String is {firstStringMethod()}");

            var balance = new Currency(34.50);

            firstStringMethod = balance.ToString;

            Console.WriteLine($"String is {firstStringMethod()}");

            firstStringMethod = new GetAString(Currency.GetCurrencyUnit);

            Console.WriteLine($"String is{firstStringMethod()}");

        }

    }

这段代码说明了如何通过委托来调用方法,然后重新给委托指定在类的不同实例引用的不同方法,甚至可以指定静态方法,或者指定在类的不同类型实例上引用的方法,只要每个方法的签名匹配委托定义即可

     运行此应用程序,会得到委托引用的不同方法的输出结果:

           String is 40

            String is $34.50

            String is Dollar

下面是一个简单的委托示例。定义一个类MathOperations,他有两个静态方法,对double类型的值执行两种操作。然后使用该委托调用这些方法。MathOperations类代码如下:

class MathOperations

    {

        public static double MultiplByTwo(double value) => value * 2;

        public static double Square(double value) => value * value;

}

下面调用这些方法,代码如下:

using WeiTuo;

using static System.Console;

namespace Wrox.ProCSharp.Delegates

{

    delegate double DoubleOp(double x);

    public class Program

    {

        static void Main(string[] args)

        {

            DoubleOp[] operations =

            {

                MathOperations.MultiplByTwo,

                MathOperations.Square

            };

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

            {

                WriteLine($"Using operations[{i}]:");

                ProcessAndDisplayNumder(operations[i], 2.0);

                ProcessAndDisplayNumder(operations[i], 7.9);

                ProcessAndDisplayNumder(operations[i], 1.414);

                WriteLine();

            }

        }

        static void ProcessAndDisplayNumder(DoubleOp action, double value)

        {

            double result = action(value);

            WriteLine($"Value is{value},result of operation is {result}");

        }

    }

}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值