委托和事件






4.11/5 (35投票s)
2003年8月17日
6分钟阅读

218688

2
本文简要介绍了委托、事件的概念及其在 .NET 框架基类库中的用法。
引言
委托和事件是 .NET 中两个非常强大的功能。事件是 GUI 编程的基石,而委托用于实现它们。在本文中,我将简要讨论委托的工作原理以及如何使用它们来实现事件。
目录
什么是委托?
.NET 框架中的委托等同于函数指针。但是,C# 语言规范还补充道:“与函数指针不同,委托是面向对象的且类型安全的”。
委托允许您将方法入口点的引用传递给方法,并在不进行显式方法调用的情况下调用它们。它还定义了它可以调用的方法的签名。
例如
public delegate int MyDelegate(string somevalue);
此委托可用于调用返回整数并以字符串作为参数的方法。为了使用此委托,您需要实例化它,以指定需要调用的方法。
例如
public void MyMethod()
{
MyDelegate a = new MyDelegate(MyDelegateMethod);
}
这里的 MyDelegateMethod
是一个签名与 MyDelegate
相似的方法。
注意:如果方法和委托的签名不匹配,C# 编译器会报告一个错误,例如
td.cs(38,46): error CS0123: Method 'DelegateDemo.SampleClient.MyEvent()'
does not match delegate 'void DelegateDemo.CustomTextEvent()'
委托用于调用方法,类似于进行方法调用。
例如
a(“This is a test invocation”);
因此,定义和使用委托有三个步骤
- 声明
- 实例化和
- 调用
完整的示例如下
using System;
namespace SimpleDelegateExample{
public delegate void SimpleDelegate();
class TestDelegate
{
public static void ActualFunction()
{
Console.WriteLine("called by delegate..");
}
public static void Main(){
SimpleDelegate sd = new SimpleDelegate(ActualFunction);
sd();
}
}// end class TestDelegate
}// end namespace SimpleDelegateExample
有几个有趣的点值得注意……
委托不知道也不关心它引用的对象的类。任何对象都可以;唯一重要的是方法的参数类型和返回类型与委托的匹配。这使得委托非常适合“匿名”调用。
如果被调用的方法抛出异常,该方法的执行将停止,异常将被传递给委托的调用者,并且调用列表中的剩余方法不会被调用。在调用者中捕获异常不会改变此行为。
委托是如何工作的?
.NET 框架文档详细介绍了委托的工作原理。
“委托声明定义了一个派生自
System.Delegate
类的类。委托实例封装了一个调用列表,该列表包含一个或多个方法,每个方法都称为可调用实体。对于实例方法,可调用实体包括一个实例和该实例上的一个方法。对于静态方法,可调用实体仅包含一个方法。使用适当的参数调用委托实例将导致委托实例的每个可调用实体都使用给定的参数集被调用。”
事件
C# 程序员参考对事件的定义如下:
“C# 中的事件是一种类向该类的客户端提供通知的方式,当某个对象发生有趣的事情时。事件最熟悉的用途是图形用户界面;通常,表示界面中控件的类具有在用户对控件执行某些操作时(例如,单击按钮)发出的事件通知。”
声明事件直接与委托相关联。委托对象封装了一个方法,以便可以匿名调用它。事件是一种机制,客户端类可以通过该机制传入方法委托,以便在“发生某些事情”时调用这些方法。当发生这种情况时,将调用由其客户端传递给它的委托。
要在 C# 中声明事件,请使用以下语法
public delegate void testDelegate(int a);
public event testDelegate MyEvent;
声明事件后,在引发它之前必须将其与一个或多个事件处理程序关联。事件处理程序不过是通过委托调用的方法。使用 +=
运算符将事件与现有委托实例关联。
例如
Myform.MyEvent += new testEvent(MyMethod);
事件处理程序也可以像这样分离
MyForm.MyEvent -= new testEvent(MyMethod);
在 C# 中,可以通过调用它们的名称来引发事件,类似于方法调用,例如 MyEvent(10)
。下一节中的示例将帮助您更好地理解事件。
event 关键字是如何工作的?
每当为一个类定义事件时,编译器都会生成三个用于管理底层委托的方法……即
add_<EventName>
:这是一个公共方法,它调用
System.Delegate
的静态Combine
方法,以便将另一个方法添加到其内部调用列表中。然而,此方法并不显式使用。如前所述,使用+=
运算符可以达到相同的效果。remove_<EventName>
:这同样是一个公共方法,它调用
System.Delegate
的静态Remove
方法,以便从事件的调用列表中删除一个接收器。此方法也不直接调用。它的工作由-=
运算符完成。raise_<EventName>
:一个受保护的方法,它调用委托的编译器生成的
Invoke
方法,以便调用调用列表中的每个方法。
Windows Forms 中的委托和事件
.NET Framework 基类库的控件和类的事件都提供了默认委托。使用 Windows Forms 创建富客户端应用程序时,可以通过两种方式处理事件……
- 通过声明一个签名与相应事件处理程序委托相同的方法,然后将该方法注册为处理程序(使用
+=
)。 - 通过重写相应的事件方法,当您的类是
Control
类的子类时。
例如,Windows Forms 应用程序中的 Paint
事件(相当于 WM_PAINT
)可以按如下方式处理
使用委托注册事件处理程序
声明一个与 PaintEventHandler
委托具有相同返回类型和参数的方法
static void MyPaintHandler(object sender, PaintEventArgs pea){
// process
}
然后为特定实例安装此处理程序……
myform.Paint += new PaintEventHandler(MyPaintHandler);
重写成员函数
在派生自 Form
(Control
) 的类中,您可以简单地重写 OnPaint
方法
protected override void OnPaint(PaintEventArgs pea){
// process
}
此时,认为基类方法(可以重写以处理事件)只是一个已经安装好的事件处理程序是很自然的。实际上是相反的。基类方法负责实际调用所有已安装的事件处理程序。我举一个例子来说明这一点
using System;
using System.Windows.Forms;
namespace DelegateDemo{
public delegate void CustomTextEvent();
class CustomTextForm : Form
{
private string m_sText=null;
public event CustomTextEvent CustomTextChanged;
public string CustomText
{
get
{
return m_sText;
}
set
{
m_sText = value;
OnCustomTextChanged();
}
}
protected virtual void OnCustomTextChanged()
{
if(CustomTextChanged!= null)
CustomTextChanged();
}
}// end class CustomTextForm
class SampleClient
{
public static void Main(){
CustomTextForm a = new CustomTextForm();
a.CustomTextChanged += new CustomTextEvent(SampleClient.MyEvent);
a.CustomText = "Welcome !";
Application.Run(a);
}
public static void MyEvent()
{
Console.WriteLine("Custom text changed");
}
}// end class SampleClient
}// end namespace SampleDelegateDemo
代码讨论
为方便讨论,假设您需要编写一个自定义的 Form
类,它有一个名为 CustomText
的属性。它实际上是一个可以设置或获取的字符串。每当文本被设置时,都会触发一个事件。这被声明为
public event CustomTextEvent CustomTextChanged;
并且也定义了相应的委托
public delegate void CustomTextEvent();
还有一个基类(您的类)的事件处理方法,该方法在事件发生时被调用。如下所示
protected virtual void OnCustomTextChanged()
{
if(CustomTextChanged!= null)
CustomTextChanged();
}
客户端类还注册了一个新的事件处理程序……
a.CustomTextChanged += new CustomTextEvent(SampleClient.MyEvent);
运行此示例时,当 CustomText
属性更改时,将调用 MyEvent
方法。
注意:
以下代码是必需的
if(CustomTextChanged!= null)
CustomTextChanged();
因为 CustomTextChanged
可能是 null
,如果尚未注册任何处理程序,则在设置 CustomText
属性时。
OnCustomChanged
方法被声明为 virtual
,以便子类可以重写它。
延伸阅读
一旦您对委托非常熟悉,您就会发现以下文章很有趣
- C# 委托的黑暗面:Dumky
- 编译器生成的委托方法:MSDN