C#笔记8:事件
本章概要:
1:事件处理程序签名遵循的约定
2:事件处理程序的参数
3:事件中的异常
4:接口中的事件
5:自定义事件访问器
1:事件处理程序签名遵循的约定
-
返回类型为 Void。
-
第一个参数命名为 sender,是 Object 类型。它是引发事件的对象。
-
第二个参数命名为 e,是 EventArgs 类型或 EventArgs 的派生类。它是特定于事件的数据。
-
该方法有且仅有两个参数。
-
使用 System.EventHandler或System.EventHandler<T>,而不要手动创建用作事件处理程序的新委托。
-
考虑使用 System.EventArgs 的派生类作为事件参数,除非您完全确定事件决不会需要向事件处理方法传递任何数据(这种情况下可以直接使用 System.EventArgs 类型)。
-
使用受保护的虚方法来引发每个事件。这只适用于未密封类的非静态事件,而不适用于结构、密封类或静态事件。
遵循此准则可使派生类能够通过重写受保护的方法来处理基类事件。受保护的 virtual(在 Visual Basic 中是 Overridable)方法的名称应该是为事件名加上 On 前缀而得到的名称。例如,名为“TimeChanged”的事件的受保护的虚方法被命名为“OnTimeChanged”。
2:事件处理程序的参数
当引发非静态事件时,不要将 null(在 Visual Basic 中为 Nothing)作为 sender 参数进行传递。
对于静态事件,sender 参数应该为 null(在 Visual Basic 中是 Nothing)。
当引发事件时,不要将 null(在 Visual Basic 中为 Nothing)作为事件数据参数进行传递。
如果没有事件数据,则传递 Empty,而不要传递 null。
3:事件中的异常
事件处理方法中会发生任意代码执行,对此一定要做好准备。
考虑将引发事件的代码放置在 try-catch 块中,以防止由事件处理程序引发的未经处理的异常所导致的程序终止。
4:接口中的事件
首先,需要认识到的一点是:接口可声明事件。
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
下面的示例演示如何处理以下的不常见情况:您的类是从两个以上的接口继承的,每个接口都含有同名事件)。在这种情况下,您至少要为其中一个事件提供显式接口实现。为事件编写显式接口实现时,必须编写 add 和 remove 事件访问器。这两个事件访问器通常由编译器提供,但在这种情况下编译器不能提供。
namespace WrapTwoInterfaceEvents
{
using System;
public interface IDrawingObject
{
// Raise this event before drawing
// the object.
event EventHandler OnDraw;
}
public interface IShape
{
// Raise this event after drawing
// the shape.
event EventHandler OnDraw;
}
// Base class event publisher inherits two
// interfaces, each with an OnDraw event
public class Shape : IDrawingObject, IShape
{
// Create an event for each interface event
event EventHandler PreDrawEvent;
event EventHandler PostDrawEvent;
object objectLock = new Object();
// Explicit interface implementation required.
// Associate IDrawingObject's event with
// PreDrawEvent
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (objectLock)
{
PreDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PreDrawEvent -= value;
}
}
}
// Explicit interface implementation required.
// Associate IShape's event with
// PostDrawEvent
event EventHandler IShape.OnDraw
{
add
{
lock (objectLock)
{
PostDrawEvent += value;
}
}
remove
{
lock (objectLock)
{
PostDrawEvent -= value;
}
}
}
// For the sake of simplicity this one method
// implements both interfaces.
public void Draw()
{
// Raise IDrawingObject's event before the object is drawn.
EventHandler handler = PreDrawEvent;
if (handler != null)
{
handler(this, new EventArgs());
}
Console.WriteLine("Drawing a shape.");
// RaiseIShape's event after the object is drawn.
handler = PostDrawEvent;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
public class Subscriber1
{
// References the shape object as an IDrawingObject
public Subscriber1(Shape shape)
{
IDrawingObject d = (IDrawingObject)shape;
d.OnDraw += new EventHandler(d_OnDraw);
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub1 receives the IDrawingObject event.");
}
}
// References the shape object as an IShape
public class Subscriber2
{
public Subscriber2(Shape shape)
{
IShape d = (IShape)shape;
d.OnDraw += new EventHandler(d_OnDraw);
}
void d_OnDraw(object sender, EventArgs e)
{
Console.WriteLine("Sub2 receives the IShape event.");
}
}
public class Program
{
static void Main(string[] args)
{
Shape shape = new Shape();
Subscriber1 sub = new Subscriber1(shape);
Subscriber2 sub2 = new Subscriber2(shape);
shape.Draw();
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
/* Output:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Sub2 receives the IShape event.
*/
5:自定义事件访问器
事件是特殊类型的多路广播委托,只能从声明它的类中调用。 客户端代码通过提供对应在引发事件时调用的方法的引用来订阅事件。 这些方法通过事件访问器添加到委托的调用列表中,事件访问器类似于属性访问器,不同之处在于事件访问器被命名为 add 和 remove。 在大多数情况下都不需要提供自定义的事件访问器。 如果您在代码中没有提供自定义的事件访问器,编译器会自动添加事件访问器。 但在某些情况下,如上文代码,你就需要自定义事件访问器。