c#委托和事件

.NET Framework 使VB.NET真正具有了面向对象的功能,并且还引入了一种完全面向对象的语言C#。通过使用这些语言,用户可以创建模拟实际商业环境的健壮的事件驱动的应用程序。例如,在现实世界中,操作人员通过对事件做出响应来交互。当某个操作人员执行操作并触发事件后,另一个操作人员会根据这个事件执行相应的操作,这就形成了一个对话或场景。这些操作相互关联,但由相互独立的操作人员执行。为了反映现实世界,面向对象的程序需要这样的机制:在保持对象之间交互能力的同时允许对象实例之间相互独立。事件和委托就提供了这样的一种机制。

委托

 

图 4‑1 委托

委托是引用类型,这与类和接口相似。用户可以声明委托类型,定义委托类型的变量或者创建委托的实例。委托可以在运行时间接调用一个或多个方法。在声明委托类型或基于委托类型的变量时并不指定委托将要调用哪些方法,这一操作是在创建委托的实例时才进行的,并且在运行时还可以将一个或多个方法与委托动态关联,如图 4‑1所示。委托与C++中的函数指针类似。但是,与 C++ 函数指针不同的是,它们是类型安全的对象,并且具有各自属性和方法。另外,委托的签名(包含参数和返回值的类型)必须与其调用的方法的签名匹配。下面的代码演示了如何实现委托。

[C#]

//声明一个委托

public delegate void Handler(Object obj);

//声明和委托签名相同的方法

public void DoSomething1(Object obj)

{

       //在方法中执行操作。

}

public void DoSomething2(Object obj)

{

       //在方法中执行操作。

}

 

//在方法中使用委托

public void UseHandler()

{

//创建一个委托,这时只有一个方法与之关联,它就是单路广播委托。

Handler handler = new Handler(DoSomething1);

//关联第二个方法,它就是多路广播委托。

handler += new Handler(DoSomething2);

//调用委托,效果和调用与之关联的方法相同。

handler("测试对象");

}

 

[VB.NET]

    '声明一个委托

    Public Delegate Sub Handler(ByVal obj As Object)

    '声明和委托签名相同的方法

    Public Sub DoSomething1(ByVal obj As Object)

        '在方法中执行操作。

    End Sub

    Public Sub DoSomething2(ByVal obj As Object)

        '在方法中执行操作。

    End Sub

 

    '在方法中使用委托

    Public Sub UseHandler()

        '创建一个委托,这时只有一个方法与之关联,它就是单路广播委托。

        Dim handler1 As New Handler(AddressOf DoSomething1)

        Dim handler2 As New Handler(AddressOf DoSomething2)

 

        '关联第二个方法,它就是多路广播委托。

        Dim handler3 As Handler = Handler.Combine(handler1, handler2)

        handler3.Invoke("测试数据")

End Sub

在C#中,用户可以创建匿名方法。要创建匿名方法,在实例化委托时需要创建方法签名并提供内联代码块。只要签名匹配,委托就可保存对多个已命名方法的引用。如果委托只引用一个方法,则认为该委托是单路广播委托。当向委托添加更多指向其他方法的引用时,这些引用将被存储在委托的调用列表中,这种委托就是多路广播委托。所有委托都是隐式的多路广播委托。通过向一个委托的调用列表添加多个方法引用,可以通过调用该委托一次调用所有的方法。这一过程称为多路广播。用户也可以单独调用调用列表中的每个方法。如果想要确定每个方法调用的返回值或输出参数或者如果不想在特定环境中调用特定的方法,这样做是有用的。

通过使用 Delegate 类间接调用应用程序方法

用户可以使用 Delegate 类实例化对象,使对象能作为指向函数、静态方法或实例方法的引用。由于委托实例为引用类型,因此可将它们作为参数传递给其他方法。声明委托时,除了指定返回值外还必须指定一个方法签名。当实例化委托时,需要引用该方法。该方法必须与委托具有相同的方法签名,并且该方法必须返回委托声明中定义的值。调用委托时,委托调用与之关联的方法。当引用的方法向委托返回值时,委托会将该值传递给调用程序,就如同该值是委托自身的返回值一样。

下面说明了使用委托的步骤。在这些步骤中我们将创建 ReportPrinter类,该类用于打印来自应用程序的多个报告。该应用程序需要来自ReportPrinter类的反馈。这样,应用程序就可以创建一个能从 ReportPrinter 类回调的方法。

  • 步骤1:声明委托

在该步骤中,首先创建一个名为 PrintStatus 的枚举。该枚举包含有关当前打印状态的信息。接下来,声明一个名为 PrintInfoCallBack 的委托。调用应用程序具有一个与该方法签名匹配的方法。

[C#]
        public enum PrintStatus

        {

            GeneratingReport,

            GeneratedReport,

            PrintingReport,

            PrintingComplete,

            Error

        }

 

        public delegate void PrintInfoCallBack(PrintStatus Status, object State);

 

[VB.NET]
    Public Enum PrintStatus

        GeneratingReport

        GeneratedReport

        PrintingReport

        PrintingComplete

        [Error]

    End Enum

    Public Delegate Sub PrintInfoCallBack(ByVal Status As PrintStatus, _

    ByVal State As Object)

  • 步骤2:创建 ReportPrinter 类,使用委托

在该步骤中我们创建一个在打印报告时调用的PrintNextReport方法的ReportPrinter 类。应用程序创建ReportPrinter类的一个实例并调用PrintNextReport方法。该方法接受基于在步骤1中创建的 PrintInfoCallBack委托数据类型的参数。四次调用名为CallBack的 PrintInfoCallBack 委托:在报告生成前、在报告生成后、在开始打印报告前以及在完成报告打印后。每次都会向CallBack方法返回不同的状态。

[C#]
        public class ReportPrinter

        {

            public void PrintNextReport(PrintInfoCallBack CallBack, object State)

            {

                CallBack(PrintStatus.GeneratingReport, State);

                CallBack(PrintStatus.GeneratedReport, State);

                CallBack(PrintStatus.PrintingReport, State);

                CallBack(PrintStatus.PrintingComplete, State);

            }

        }

 

[VB.NET]
    Public Class ReportPrinter

 

        Public Sub PrintNextReport(ByVal CallBack As PrintInfoCallBack, _

    ByVal State As Object)

            CallBack(PrintStatus.GeneratingReport, State)

            CallBack(PrintStatus.GeneratedReport, State)

            CallBack(PrintStatus.PrintingReport, State)

            CallBack(PrintStatus.PrintingComplete, State)

        End Sub

    End Class

使用回调时应当小心。在这种情况下,ReportPrinter 类不知道在调用委托时将发生的事件。发生的事件由调用应用程序决定。应该避免在性能敏感或安全敏感方法中使用回调模式,这是因为调用应用程序可能无法正常执行回调或回调的执行方式可能不安全。

  • 步骤3:创建委托调用的方法

在该步骤中,我们使用 ReportPrinter 类创建一个控制台应用程序并添加一个签名与 PrintInfoCallBack 委托匹配的方法。从 ReportPrinter 类回调 GetPrintInfo 方法并向命令控制台显示状态信息。在 Main 子例程中创建名为 rp 的 ReportPrinter 类的实例,并调用其 PrintNextReport 方法两次以生成两个报告。将两个参数传递给该方法。第一个参数为新的指向调用应用程序的 GetPrintInfo 方法的PrintInfoCallBack 委托。第二个参数为包含状态的对象,该参数将被传递回回调方法。由于该状态将被传递回回调方法,因此可为应用程序想要跟踪的任何信息。

当控制台应用程序启动时,Main方法创建ReportPrinter类的实例并调用ReportPrinter.PrintNextReport方法。ReportPrinter 类回调控制台应用程序,这是因为 ReportPrinter.PrintNextReport方法调用控制台应用程序的GetPrintInfo方法。

[C#]
        class Program

        {

            static void GetPrintInfo(PrintStatus Status, object State)

            {

                Console.WriteLine("{0} Print Status = {1}", State, Status);

            }

 

            static void Main(string[] args)

            {

                ReportPrinter rp = new ReportPrinter();

                rp.PrintNextReport(new PrintInfoCallBack(Program.GetPrintInfo), "First Report");

                rp.PrintNextReport(new PrintInfoCallBack(Program.GetPrintInfo), "Second Report");

            }

        }

 

[VB.NET]
Class Program
    Shared Sub GetPrintInfo(ByVal Status As PrintStatus, _
ByVal State As Object)
        Console.WriteLine("{0} Print Status = {1}", State, Status)
    End Sub
    Shared Sub Main(ByVal args As String())
        Dim rp As ReportPrinter = New ReportPrinter
        rp.PrintNextReport(AddressOf GetPrintInfo, "First Report")
        rp.PrintNextReport(AddressOf GetPrintInfo, "Second Report")
    End Sub
End Class

 

表 4‑1描述了Delegate类的成员。

表 4‑1

成员

描述

Method 属性

返回委托引用的方法的只读属性。如果委托引用了多个方法,则 Method 属性将返回委托的调用列表中的最后一个方法。

Target 属性

返回包含委托引用的方法的对象的只读属性。如果方法是静态的,则返回的值为 null。如果委托引用了多个方法,则 Target 属性将返回包含调用列表中最后一个方法的对象。

Combine 方法

将委托数组的调用列表串连在一起的静态方法。可使用该方法将多个委托的调用列表组合到一个委托中。

CreateDelegate 方法

可用来创建引用实例或静态(VB.NET中为共享)方法的委托的静态方法。

DynamicInvoke 方法

该方法调用委托的调用列表中的方法。被调用的方法的参数以数组的形式传递。如果委托不使用参数,则参数数组应为 Null。

GetInvocationList 方法

该方法返回一个单路广播委托数组,表示当前委托的调用列表。如果委托只引用一个方法,则该数组将仅包含一个引用其自身的元素。如果委托为多路广播,则该数组包含多个元素。

Remove 方法

将指定委托的调用列表的最后一个实例从另一个委托中移除的静态方法。

RemoveAll 方法

将指定委托的调用列表的所有实例从另一个委托中移除的静态方法。

在以下代码示例中,修改Program类以将多路广播委托传递给PrintNextReport方法。创建两个方法GetPrintInfo1和GetPrintInfo2。创建名为picb的多路广播委托。这个委托将被传递给PrintNextReport方法,并且按照方法被添加到委托中的顺序依次调用这些方法。在这种情况下,首先调用 GetPrintInfo1,然后调用 GetPrintInfo2。注意C#允许使用加法复合赋值(+=)运算符将两个委托合并,而VB.NET则要求使用委托类型的Combine方法才能达到同样目的。

[C#]
        class Program

        {

            static void GetPrintInfo1(PrintStatus Status, object State)

            {

                Console.WriteLine("{0} Print Status " + "1 = {1}", State, Status);

            }

 

            static void GetPrintInfo2(PrintStatus Status, object State)

            {

                Console.WriteLine("{0} Print Status " + "2 = {1}", State, Status);

            }

 

            static void Main(string[] args)

            {

                ReportPrinter rp = new ReportPrinter();

                PrintInfoCallBack picb = new PrintInfoCallBack(Program.GetPrintInfo1);

                picb += new PrintInfoCallBack(Program.GetPrintInfo2);

                rp.PrintNextReport(picb, "First Report");

                rp.PrintNextReport(picb, "Second Report");

            }

        }

[VB.NET]
    Class Program

        Shared Sub GetPrintInfo1(ByVal Status _

    As PrintStatus, ByVal State As Object)

            Console.WriteLine("{0} Print " & _

    "Status 1 = {1}", State, Status)

        End Sub

 

        Shared Sub GetPrintInfo2(ByVal Status _

    As PrintStatus, ByVal State As Object)

            Console.WriteLine("{0} Print " & _

    "Status 2 = {1}", State, Status)

        End Sub

 

        Shared Sub Main(ByVal args As String())

            Dim rp As ReportPrinter = New _

    ReportPrinter

            Dim picb1 As New PrintInfoCallBack _

    (AddressOf GetPrintInfo1)

            Dim picb2 As New PrintInfoCallBack _

    (AddressOf GetPrintInfo2)

            Dim picb As PrintInfoCallBack = _

    PrintInfoCallBack.Combine(picb1, picb2)

            rp.PrintNextReport(picb, _

    "First Report")

            rp.PrintNextReport(picb, _

    "Second Report")

        End Sub

    End Class

以下代码示例给出了当实例化委托时使用泛型委托声明来动态指派委托的返回类型的过程。在该示例中,声明了一个包含星期中每一天的名为days的枚举。然后,声明了一个名为 GetDay 的泛型委托声明。GetDay委托定义一个 T 返回类型。在创建 GetDay 委托的新实例时指定返回类型。在Program类中有两个函数。第一个函数将days枚举作为一个参数并返回一个整数,而第二个函数返回一个字符串。在 Main 方法中创建并实例化了两个 GetDay 类型的委托,分别为 Position 和 Name。当声明这些委托时,指定 Name 将返回字符串,Position 将返回整数。调用每个委托并将输出写入命令控制台。注意调用 Position 返回一个整数,而调用 Name 返回一个字符串。

[C#]
        public enum days

        {

            Sun = 1,

            Mon,

            Tue,

            Wed,

            Thu,

            Fri,

            Sat

        }

 

        public delegate T GetDay<T>(days Day);

 

        class Program

        {

            public static int GetDayPosition(days Day)

            {

                return (int)Day;

            }

 

            public static string GetDayName(days Day)

            {

                return Day.ToString();

            }

 

            static void Main(string[] args)

            {

                GetDay<int> Position = GetDayPosition;

                GetDay<string> Name = GetDayName;

                Console.WriteLine(Position(days.Fri));

                Console.WriteLine(Name(days.Fri));

            }

        }

 

[VB.NET]
Public Enum days
    Sun = 1
    Mon
    Tue
    Wed
    Thu
    Fri
    Sat
End Enum
Delegate Function GetDay(Of T)(ByVal _
Day As days) As T
Class Program
    Public Shared Function _
GetDayPosition(ByVal Day As days) As _
Integer
        Return CType(Day, Integer)
    End Function
    Public Shared Function GetDayName _
(ByVal Day As days) As String
        Return Day.ToString()
    End Function
    Shared Sub Main()
        Dim Position As New GetDay _
(Of Integer)(AddressOf GetDayPosition)
        Dim Name As New GetDay _
(Of String)(AddressOf GetDayName)
        Console.WriteLine(Position(days.Fri))
        Console.WriteLine(Name(days.Fri))
    End Sub
End Class

通过使用新的 C# 2.0技术增强委托行为

C# 2.0 对委托的语言进行了某些增强,引入了匿名方法、协变和逆变。这些新的增强使用户能编写更加高效和灵活的代码。匿名方法允许你通过指定要被委托调用的内联代码块而不是现有方法来创建委托的实例。VB.NET不支持匿名方法。

在.NET Framework 以前的版本中,有这样的规则:委托的签名(包含参数和返回值的类型)必须与其调用的方法的签名匹配。编译器会将这条规则强加给委托。如果签名不匹配,调用的方法就无法保证其具有正确类型的数据。但是在C# 2.0中两个例外情况,那就是协变和逆变。

(1)         匿名方法

匿名方法是一个新功能,它允许定义一个由委托调用的匿名(也就是没有名称的)方法。要将代码块传递为委托参数,创建匿名方法则是唯一的方法。例如:

[C#]

// 创建一个委托实例委托

delegate void Del(int x);

//使用一个匿名方法实例化委托

Del d = delegate(int k) { /* ... */ };

从上面的例子可以看出,如果使用匿名方法,则不必创建单独的方法,因此减少了实例化委托所需的编码系统开销。例如,如果创建方法所需的系统开销是不必要的,在委托的位置指定代码块就非常有用。启动新线程即是一个很好的示例。无需为委托创建更多方法,线程类即可创建一个线程并且包含该线程执行的代码。

[C#]

void StartThread()

{

System.Threading.Thread t1 = new System.Threading.Thread (delegate()

{

        System.Console.Write("Hello, ");

        System.Console.WriteLine("World!");

});

t1.Start();

}

(2)         协变

当委托方法的返回类型具有的派生程度比委托签名更大时,就称为协变委托方法。因为方法的返回类型比委托签名的返回类型更具体,所以可对其进行隐式转换。这样该方法就可用作委托。协变使得创建可被类和派生类同时使用的委托方法成为可能。

以下代码示例使用了两个方法FirstHandler和SecondHandler。FirstHandler方法返回 Mammal,而SecondHandler方法返回Dog。HandlerMethod委托声明返回类型为Mammal。协变允许从HandlerMethod委托调用SecondHandler方法,因为Dog是从Mammal派生出来的。只能在C#中使用协变。

[C#]
        class Mammal

        {

        }

        class Dog : Mammal

        {

        }

        class Program

        {

            public delegate Mammal HandlerMethod();

            public static Mammal FirstHandler()

            {

                return null;

            }

            public static Dog SecondHandler()

            {

                return null;

            }

            static void Main()

            {

                HandlerMethod handler1 = new HandlerMethod(FirstHandler);

                HandlerMethod handler2 = new HandlerMethod(SecondHandler);

            }

        }

(3)         逆变

当委托方法签名具有一个或多个参数,并且这些参数的类型派生自方法参数的类型时,就称为逆变委托方法。因为委托方法签名参数比方法参数更具体,因此可以在传递给处理程序方法时对它们进行隐式转换。这样逆变使得可由大量类使用的更通用的委托方法的创建变得更加简单。

例如,如果方法具有两个参数,则委托的第一个参数必须继承于方法的第一个参数的类,且第二个参数必须继承于第二个参数的类。在以下代码中有两个方法:FirstHandler 和 SecondHandler。FirstHandler方法接受参数Mammal,而 SecondHandler方法接受参数 Dog。HandlerMethod委托声明声明参数类型为Dog。逆变允许从HandlerMethod委托调用 FirstHandler 方法,因为Dog是从Mammal派生出来的。

只能在 C# 中使用逆变。

[C#]


        class Program

        {

            public delegate void HandlerMethod(Dog sampleDog);

            public static void FirstHandler(Mammal elephant)

            {

            }

            public static void SecondHandler(Dog sheepDog)

            {

            }

            static void Main(string[] args)

            {

                HandlerMethod handler1 = FirstHandler;

                HandlerMethod handler2 = SecondHandler;

            }

        }

VB.NET不支持协变和逆变。以下代码示例将前一示例中使用的报告打印应用程序的 Main子例程转换为在新的线程上异步调用PrintNextReport方法。通常,创建线程的新实例时,将需要根据ThreadStart委托创建命名的方法。在以下代码中,当创建 System.Threading.Thread的新实例时,将匿名方法传递给构造函数。匿名方法调用 PrintNextReport 方法。只能在 C# 中使用匿名方法。

[C#]


        static void Main(string[] args)

        {

            ReportPrinter rp = new ReportPrinter();

            PrintInfoCallBack picb = new PrintInfoCallBack(Program.GetPrintInfo1);

            picb += new PrintInfoCallBack(Program.GetPrintInfo2);

            System.Threading.Thread t = new System.Threading.Thread(delegate()

            {

                rp.PrintNextReport(picb, "First Report");

            });

            t.Start();

            t = new System.Threading.Thread(delegate()

            {

                rp.PrintNextReport(picb, "Second Report");

            });

            t.Start();

        }

事件

对事件的支持对于创建动态的面向对象的程序至关重要。面向对象的编程旨在创建一种使现实世界中的对象实现交互的模型。如果没有事件,对象实例在某一时间将只能影响一个对象,并且只能访问它们能感知到的对象。但是,在现实世界中,一个操作可能会导致多个反应,其中包括一些甚至连源操作人员都没觉察到的反应。事件提供了将操作方与反应方区分开来的能力,还提供了动态响应在程序执行期间发生的情况的能力。事件将不同的对象实例联系起来以形成可使用的程序。

当有意义的事情发生时,事件是一个类通知另一个类的途径。它是事件驱动编程的基础。如果想要在事件发生时执行某些操作可以给事件添加事件处理程序。通过使用事件,可以创建相互独立的类而仍保持它们能够在运行时相互交互。例如,包含 UI 控件的类可能定义一个将在用户单击该控件时发生的事件。控件类并不关心单击按钮时发生了什么,但是它需要告知其他类发生了单击事件。然后其他类可选择想要作为响应执行的操作(如果有的话)。

使用事件的好处

包含在.NET Framework中的对象类提供了大量的预构建事件,如 Click、KeyDown 和 Paint。如果创建了自己的类,且该类继承于任一 .NET Framework 对象类,则你将自动获得对这些预构建事件的访问权。不仅如此,我们还可创建自己的自定义事件。

.NET Framework 通常会在对象发生某些情况时自动引发预构建的事件作为响应。例如,如果创建继承自基本 Control 类的自定义控件,则将自动获取对诸如 Click 等事件的支持。

当用户在 UI 中单击自定义控件时,该控件就会引发 Click 事件。但是,也可以通过使用 RaiseEvent 命令在VB.NET中手动引发事件,该命令在内部调用该事件的委托;而在 C# 中,必须手动调用提供事件基础的委托。在引发事件之前,可能想要检查事件处理程序是否已经与某个委托相关联。可通过检查事件是否为null来完成该操作。

事件的工作方式

事件是根据发布者或预订者模型设计的。触发事件的类称为发布者。想要对事件作出响应的类将一个或多个方法添加到发布者的委托中。该类称为预订者。当发布者调用委托时,也会调用预订者的方法。通过使用委托,发布者可以调用预订者中的一个或多个方法,而无需了解方法的详细信息。预订者也可以指定零个、一个或多个方法来响应发布者的事件,而无需了解事件的详细信息。大多数时间,事件都会被预订者忽略,并而且不会有与事件关联的方法。

预订者要了解的惟一内容就是发布者事件委托的参数签名。从本质上来说,事件都是单向的,它们不具有返回值;因此预订者不必考虑事件的返回值签名。

在程序中创建并使用自定义事件包含三个步骤。

  • 首先,必须在类中声明事件并确定将要使用的委托和参数。
  • 其次,必须定义在触发事件时要调用的委托。
  • 最后,必须设计事件参数类,该参数类的实例会将信息传递给被调用的方法。如果使用内置的 EventArgs 对象和 EventHandler 委托,则可以不执行最后两个步骤。

注意:虽然可以在创建类时创建事件,但却不能保证该事件将被调用类或预订者类调用。

通过使用 Event 语句创建事件

.NET Framework 提供 Event 语句,可以在需要创建自定义事件时使用该语句。例如,我们要编写一个自定义单选按钮控件。标准单选按钮的选中状态只有在单击同组内的另一个单选按钮时才会重置。因此,标准单选按钮只预定义了Click 和CheckedChange 事件。但是,我们的控件允许用户通过单击已经选中的单选按钮来取消选择。为此,我们可以创建一个称为unchecked的事件并在用户单击已经选中的单选按钮时触发该事件。可以使用 Event 语句标识事件触发时将要调用的委托的签名。在VB.NET中, Event、RaiseEvent、AddHandler 和 RemoveHandler 等命令抽象化了底层委托的创建和处理。在C# 中,可以使用 event 关键字标识将用于该事件的委托声明。VB.NET开发人员也可直接通过 Delegate 关键字访问委托。在VB.NET和 C# 中,Event 语句用于向类添加事件声明。由于在 C# 中声明事件时必须指定委托,所以可使用 EventHandler 委托,而不必声明自己的自定义委托。.NET Framework 中预定义的 EventHandler 委托具有两个参数,它们的类型分别为 Object 类型和 EventArgs类型,并且没有返回值。Object 参数通常为 sender,它将触发事件的对象传递给事件处理程序。EventArgs参数通常为 e,它将 System.EventArgs 类的新实例传递给事件处理程序。

(4)         使用VB.NET实现

在VB.NET中,可以通过使用默认的委托或具有特定签名的自定义委托将一个事件声明为简单事件。

Public Event SomethingHappened()
Public Event SomethingHappened As EventHandler
Public Event SomethingElseHappened As CustomEventDelegate

可以通过使用下列代码语句在程序中的任何地方声明在上面的代码示例中使用的CustomEventDelegate。

Public Delegate Sub CustomEventDelegate(ByVal sender As Object, ByVal e As EventArgs)

(5)         使用C#实现

在C#中,始终通过使用委托签名声明事件,但是也可使用诸如EventHandler等预定义的委托或具有特定签名的自定义委托。

public event EventHandler SomethingHappened;

public event CustomEventDelegate SomethingElseHappened;

可以通过使用下列代码语句在程序中的任何地方声明在上面的代码示例中使用的 CustomEventDelegate。

public delegate void CustomEventDelegate(object sender, EventArgs e);

EventHandler 委托提供了一种供一个或多个方法预订事件并在事件发生时对其进行处理的机制。例如,要编写一个仓库库存管理应用程序。应用程序从关系数据库中读取库存数据,然后将这些数据作为产品对象存入的自定义集合里,以便进行后续操作。每当更新集合中产品的数量时,需要执行一些操作。需要调用一个方法来更新关系数据库。还需要将更新记录到库存审核日志中。如果库存低于再订购点,则需要调用一个方法通知供货商。将要使用的自定义集合是由其他编程人员开发的,但是你知道其中包含一个称为 QuantityChanged 的事件,可以向QuantityChangedEventHandler委托添加三个需要调用的方法。被事件调用的方法的签名必须始终与用来实例化事件的委托的签名匹配。可以像声明 EventHandler 委托的方法一样在 .NET Framework 2.0 中使用泛型声明 EventHandler 委托。如果被调用的方法的签名不与 EventHandler 的签名匹配,则将抛出一个异常。一旦 EventHandler 委托与某个特定事件关联,预订者会将引发事件时要调用的方法添加到委托的调用列表中。由于委托支持多路广播,可以向 EventHandler 委托的调用列表添加所需数量的方法。

EventHandler 委托的实现方法。

(6)         VB.NET

在VB.NET中有两种向EventHandler添加方法的途径。可以在设计时对方法使用 Handles关键字,也可以在运行时使用AddHandler语句。

第一条代码语句在设计时对方法使用Handles关键字,并将 RadioButton1_CheckedChanged方法分配给RadioButton1.CheckChanged EventHandler。第二条语句在运行时使用AddHandler语句并将AuditLog.RecordQuantityChange方法分配给 Inventory.product事件的EventHandler委托。

Private Sub RadioButton1_CheckedChanged(ByVal sender As _System.Object, ByVal e As System.EventArgs) Handles RadioButton1.CheckedChanged
End Sub
    Public Sub InitMyComponents()
    AddHandler Inventory.product, AddressOf _AuditLog.RecordQuantityChange

End Sub

可修改该代码示例以执行多个操作。通过使用以下代码示例中显示的多路广播模型可产生一个事件。在该代码示例中,当发生 RadioButton1.CheckedChanged 事件时会调用 RadioButton1_CheckedChanged 和 ManageStateChange 这两个方法。这就是多路广播行为。此外RadioButton1.CheckedChanged和RadioButton2.CheckedChanged事件也会触发 ManageStateChange 事件处理程序的执行。

Private Sub RadioButton1_CheckedChanged(ByVal sender As _
System.Object, ByVal e As System.EventArgs)
Handles RadioButton1.CheckedChanged
    End Sub
Private Sub ManageStateChange(ByVal sender As _
System.Object, ByVal e As System.EventArgs) Handles
RadioButton1.CheckedChanged, RadioButton2.CheckedChanged
    End Sub

在该代码示例中,当激发Inventory.product事件时将调用 AuditLog.RecordQuantityChange和TraceLog.TrackRecordQuantityChange这两个方法。这也是多路广播行为。

AddHandler Inventory.product, AddressOf _
AuditLog.RecordQuantityChange
    AddHandler Inventory.product, AddressOf _
TraceLog.TrackRecordQuantityChange

注意:使用 AddHandler 语句可确保方法按照其被加入调用列表的顺序执行。

可使用以下语句通过编程将处理程序从调用列表中移除:

RemoveHandler Inventory.product, AddressOf _
TraceLog.TrackRecordQuantityChange

(7)         C#

C#中没有与VB.NET中的Handles语句等价的语句。要在 C# 中预订事件处理程序,可使用 Addition Assignment 运算符(+=)将方法添加到分配给事件的 EventHandler 委托中。以下代码语句通过使用 += 运算符将AuditLog.RecordQuantityChange方法分配给 Inventory.product 事件的 EventHandler 委托。

Inventory.product += AuditLog.RecordQuantityChange;

在以下代码示例中,当激发Inventory.product事件时将调用两个方法。这是多路广播行为。将按照使用 += 运算符添加的顺序调用这些方法。

Inventory.product += AuditLog.RecordQuantityChange;
Inventory.product += TraceLog.TrackRecordQuantityChange;

可使用以下代码语句通过编程将处理程序从调用列表中移除:

Inventory.product -= TraceLog.TrackRecordQuantityChange;

自定义事件参数类

用户可以通过使用继承自 EventArgs的类将参数传递给处理事件的方法。换句话说,每当想要将来自事件发布者的特定数据传递给预订者方法,就必须使用继承自 EventArgs 基类的自定义类。例如,考虑这样的情况,用户想要编写一个只要特定产品的数量低于某个水平就会引发一个再订购事件的库存应用程序。需要将库存产品的数量包含在一个参数中,并将这个参数传递给向供货商发送订货单的方法,使该方法知道订货的数量。要完成该操作,需要创建一个继承自EventArgs基类并包含名为QuantityOnHand的只读属性的类 ReorderEventArgs。该属性被标记为只读以确保其不能在事件处理程序中被更改。如果想要允许来自事件处理程序的反馈,可以提供一个可读/写的属性。可以用任何参数创建事件处理程序,但是好的习惯是使用两个参数(Object 参数和继承自 EventArgs 的参数)的签名。Object 参数将触发事件的对象传递给处理事件的方法,而使用第二个参数将数据传递给处理事件的方法。

下面的代码示例显示了EventArgs类的实现方法。在该示例中,定义了一个继承自 EventArgs的ReorderEventArgs类。该类具有一个称为QuantityOnHand的属性,该属性在类的实例化时设置,然后这个实例化的对象作为参数传递给调用的方法。

[C#]

using System;

 

namespace MyTest

{

    class MyTestEventClass

    {

        private void x_SomethingHappened(ReorderEventArgs e)

        {

            System.Diagnostics.Debug.WriteLine(e.QuantityOnHand);

        }

 

        public MyTestEventClass()

        {

            this.SomethingHappened+=new SomethingHappenedEventHandler(x_SomethingHappened);

        }

 

        static void Main()

        {

        }

 

        public event SomethingHappenedEventHandler SomethingHappened;

    }

 

    public class ReorderEventArgs : EventArgs

    {

        private int _quantity;

 

        public ReorderEventArgs(int quantity)

        {

            _quantity = quantity;

        }

 

        public int QuantityOnHand

        {

            get { return _quantity; }

        }

    }

    public delegate void SomethingHappenedEventHandler(ReorderEventArgs e);

}

 

[VB.NET]

Module Module1

    Public Class ReorderEventArgs

        Inherits EventArgs

        Private _quantity As Integer

 

        Public Sub New(ByVal quantity As Integer)

            _quantity = quantity

        End Sub

 

        Public ReadOnly Property QuantityOnHand()

            Get

                Return _quantity

            End Get

        End Property

    End Class

    Public Class MyTestEventClass

        Public Event SomethingHappened(ByVal e As _

        ReorderEventArgs)

 

        Private Sub x_SomethingHappened(ByVal e As _

        ReorderEventArgs) Handles Me.SomethingHappened

            Debug.Print(e.QuantityOnHand)

        End Sub

 

        Shared Sub Main()

        End Sub

    End Class

End Module

事件和委托的关系

综上所述,种种迹象表明事件和委托有着密切的关系。在声明一个事件时,它的语法和声明一个委托极其相似。唯一的区别就添加event关键字。其实,可以把事件看成是特殊的委托实例,它没有返回值。虽然一般用public关键字修饰,但是事件无法在外部触发,而委托却可以在外部直接调用。图 4‑2给出了事件和委托的使用流程,帮助你更清楚了解它们之间的关系。

 

图 4‑2 事件和委托的使用流程

posted @ 2012-03-21 11:20  渡蓝  阅读(714)  评论(0)    收藏  举报