委托与事件

来源:互联网 发布:腾讯数据体育 编辑:程序博客网 时间:2024/06/08 10:49

一、委托的定义:
       C#中的委托是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为,委托方法的使用可以像其他任何方法一样具有参数和返回值。委托对象能被传递给调用该方法引用的代码而无须知道哪个方法将在编译时被调用。委托是函数的封装,它代表一“类”函数。他们都符合一定的签名:拥有相同的参数列表、返回值类型。同时委托也可以看作是对函数的抽象,是函数的“类”。此时,委托实例代表一个具体的函数。委托应该和类同属一个层面,使用起来也很象一个类。在定义委托时,必须给出它所代表的方法签名和返回类型等全部信息。

        委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。委托类型包含3个重要的信息。一是它所调用的方法的名称,二是该方法的参数(可选),三是该方法的返回值(可选)。当委托对象被创建并提供了上述信息后,它可以在运行时动态调用其指向的方法。可以看到,.NET Framework中每个委托(包括自定义委托)都被自动赋予同步或异步访问方法的能力,可以不用手工创建与管理一个Thread对象而直接调用另一个辅助执行线程上的方法,这大大简化了编程工作。

当C#编译器处理委托类型时,它先自动产生一个派生自System.MulticastDelegate的密封类。这个类与它的基类System.Delegate一起为委托提供必要的基础设施,以维护以后将要调用方法的列表。

 下面让我们看一个简单委托的例子:

namespace MySimpleDelegete
{
    //这个委托可以指向任何传入两个整数并返回一个整数的方法
    public delegate int BinaryOp(int x, int y);

    //这个类包含了MathOp指向的方法
    public class SimpleMath
    {
        public static int Add(int x, int y)
        {
            return x + y;
        }

        public static int SubStract(int x, int y)
        {
            return x - y;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("*******Simple Delegate Example ********\n");
            //创建一个指向SimpleMath.Add()方法的BinaryOp对象
            BinaryOp b = new BinaryOp(SimpleMath.Add);

            //使用委托对象间接調用Add()方法
            Console.WriteLine("10 +5 is {0}", b(10, 5));
            Console.ReadLine();
        }
    }
}

如果要将目标对象方法插入指定委托对象,只要向委托的构造函数传入方法名称即可。

//创建一个指向SimpleMath.Add()方法的BinaryOp对象
  BinaryOp b = new BinaryOp(SimpleMath.Add); 

这时,我们可以使用类似直接函数调用的语法调用指向的成员

//使用委托对象间接調用Add()方法
Console.WriteLine("10 +5 is {0}", b(10, 5));

在底层,运行库实际上在MulticastDelegate派生类上调用了编译器生成的Invoke()方法。我们可以通过在ildasm.exe中打开程序集,观察Mail()函数中CIL代码进行验证:

.method private hidebysig static void  Main(string[] args) cil managed
{
  ...

  IL_0022:  callvirt   instance int32 MySimpleDelegete.BinaryOp::Invoke(int32,
                                                                        int32)
} // end of method Program::Main

尽管C#不需要我们在代码库中显式调用Invoke(),但是我们也完全可以这么做。因此,如下代码语句是可行的:

Console.WriteLine("10 +5 is {0}", b.Invoke(10, 5));

 .NET委托是类型安全的。所以试图讲一个不“匹配模式”的方法传入委托,将会收到一个编译器错误。

 

二、委托的特点
1、一个委托对象可以搭载多个方法。
2、一个委托对象搭载的方法并不需要属于同一个类,但所搭载的方法必须具有相同的原形和形式。
3、委托的实例将代表一个具体的函数

这样记忆委托:

1、委托是一种引用方法的类型,一旦为委托分配了方法,委托将与该方法具有完全相同的行为 

2、委托的实例代表一个具体的函数,我们可以使用类似直接函数调用的语法调用指向的成员(即插入构造函数的那个方法)。

 

使用委托的三大步骤:1、定义个委托 。 1、创建一个委托的对象,并指向匹配委托签名的方法。 3、使用委托对象并传入参数,进行调用。 

 

三、为什么要使用委托
1、更加灵活的方法调用。很明显,之前的SimpleDelegate示例是纯粹用来说明委托作用的,因为仅为了加两个数创建一个委托没有多大的必要。

所以下面的例子进一步讲述委托的应用:

namespace CarDelegate{    public class Car    {        // 内部状态数据        public int CurrentSpeed { get; set; }        public int MaxSpeed { get; set; }        public string PetName { get; set; }        // 汽车能用还是不能用        private bool carIsDead;        public Car()        {            MaxSpeed = 100;        }        public Car(string name, int maxSp, int currSp)        {            CurrentSpeed = currSp;            MaxSpeed = maxSp;            PetName = name;        }        // 1) 定义委托类型        public delegate void CarEngineHandler(string msgForCaller);        // 2) 定义委托类型的成员变量        private CarEngineHandler listOfHandlers;        // 3) 向调用者添加注册函数        public void RegisterWithCarEngine(CarEngineHandler methodToCall)        { listOfHandlers += methodToCall; }        public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)        { listOfHandlers -= methodToCall; }                public void Accelerate(int delta)        {            // 如果汽车不能用了,触发引爆事件            if (carIsDead)            {                if (listOfHandlers != null)                    listOfHandlers("Sorry, this car is dead...");            }            else            {                CurrentSpeed += delta;                // 快不能用了吗?                if (10 == (MaxSpeed - CurrentSpeed)                  && listOfHandlers != null)                {                    listOfHandlers("Careful buddy!  Gonna blow!");                }                if (CurrentSpeed >= MaxSpeed)                    carIsDead = true;                else                    Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);            }        }        #endregion    }}  class Program    {        static void Main(string[] args)        {            Console.WriteLine("***** Delegates as event enablers *****\n");            // 首先,创建一个Car对像            Car c1 = new Car("SlugBug", 100, 10);            // 现在,告诉汽车,它想要向我们发送信息时调用哪一个方法            c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));            // This time, hold onto the delegate object,            // so we can unregister later.             Car.CarEngineHandler handler2 = new Car.CarEngineHandler(OnCarEngineEvent2);            c1.RegisterWithCarEngine(handler2);            // 加速(这将触发事件)            Console.WriteLine("***** Speeding up *****");            for (int i = 0; i < 6; i++)                c1.Accelerate(20);            // Unregister from the second handler.             c1.UnRegisterWithCarEngine(handler2);            // We won't see the 'upper case' message anymore!            Console.WriteLine("***** Speeding up *****");            for (int i = 0; i < 6; i++)                c1.Accelerate(20);            Console.ReadLine();        }        // We now have TWO methods that will be called by the Car        // when sending notifications.         public static void OnCarEngineEvent(string msg)        {            Console.WriteLine("\n***** Message From Car Object *****");            Console.WriteLine("=> {0}", msg);            Console.WriteLine("***********************************\n");        }        public static void OnCarEngineEvent2(string msg)        {                     Console.WriteLine("=> {0}", msg.ToUpper());        }    }


 

 这个例子为了说明,当调用Accelerate()这个方法是,根据不同的条件或情形,发送不同的消息(sending notifications)。

委托推断

C#2.0使用委托委托推断扩展了委托的语法。为了减少输入量,只要需要委托实例(即只定义一个引用),就可以只传送地址的名称。这成为委托推断。只要编译器可以把委托
实例解析为特定的类型,这个C#特性就是有效的。下面示例使用GetAString委托的一个新实例初始化了GetAString类型的变量fristStringMethod:
GetAString firstStringMethod = new GetAString(x.ToString);
只要用变量x把方法名传送给变量fristStringMethod,就可以编写出作用相同的代码:
GetAString firstStringMethod = x.ToString;
C#编译器创建的代码时一样的。编译器会用fristStringMethod检测需要的委托类型,因为创建GetAString委托类型的一个实例,用对象x把方法的地址传送给构造函数。
委托推断可以在需要委托实例的任何地方使用。委托推断可以用于事件,因为事件基于委托。

 

匿名方法
匿名方法实际上是传统意义上不存在的方法,它不是某一个类上的方法,而是纯粹为用作委托目的而创建的。
格式:delegate(parameters)
      {

      }

到目前为止,要想使委托工作,方法必须已经存在(即委托是用方法的签名定义的)。但使用委托还有另外一种方式:即通过匿名方法。匿名方法是用作委托参数的一个代码块。用匿名方法定义委托的语法与前面的定义并没有什么区别。但在实例化委托时,就有区别了。下面是一个非常简单的例子,说明如何使用匿名方法:

class Program{    delegate string DelegateTest(string val);    static void Main()     {         string mid = "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,后面是一个参数:

  delegateTest anonDel = delegate(string param)  

  {  

       param += mid;  

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

       return param;  

  }; 

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

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

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

在看一個例子:

        //發放機台        private void sendBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e)        {            SendProductAfterRepairedClientControl control = new SendProductAfterRepairedClientControl();            PropertyForm propertyForm = new PropertyForm();            propertyForm.WindowState = FormWindowState.Maximized;            propertyForm.Initialize(control);            propertyForm.OnOK += delegate(object sender1, EventArgs e1)            {                return;            };            propertyForm.ShowDialog();        }

           propertyForm.OnOK += delegate(object sender1, EventArgs e1)
            {
                return;
            };
这个匿名方法非常的简单,但意义却不简单。它在客户端注册了事件OnOk,在服务端调用事件进行就返回动作(返回到父窗体)。

 

C#事件

为了简化自定义方法的构建来为委托调用列表增加和删除方法,C#提供了event关键字。在编译器处理event关键字的时候,它会自动提供注册和注销方法以及委托类型任何必要的成员变量。这些委托成员变量总是声明为私有的,因此不能直接触发事件的对象访问它们。

定义一个事件分为两个步骤。首先,我们需要定义一个委托类型,它包含在事件触发时将要调用的方法。其次,通过C# event关键字用相关委托声明这个事件。

原创粉丝点击