在派生類中引發基類事件
以下簡單示例演示了在基類中聲明可從派生類引發的事件的標准方法。此模式廣泛應用於 .Net Framework 類庫中的 Windows 窗體類。
在創建可用作其他類的基類的類時,應考慮如下事實:事件是特殊類型的委托,只可以從聲明它們的類中調用。派生類無法直接調用基類中聲明的事件。盡管有時需要事件僅由基類引發,但在大多數情形下,應該允許派生類調用基類事件。為此,您可以在包含該事件的基類中創建一個受保護的調用方法。通過調用或重寫此調用方法,派生類便可以間接調用該事件。
注意:不要在基類中聲明虛擬事件,也不要在派生類中重寫這些事件。C# 編譯器無法正確處理這些事件,並且無法預知的該派生的事件的用戶是否真正訂閱了基類事件。
namespace
BaseClassEvents
{
using
System;
using
System.Collections.Generic;
// Special EventArgs class to hold info about Shapes.
public
class
ShapeEventArgs : EventArgs
{
private
double
newArea;
public
ShapeEventArgs(
double
d)
{
newArea = d;
}
public
double
NewArea
{
get
{
return
newArea; }
}
}
// Base class event publisher
public
abstract
class
Shape
{
protected
double
area;
public
double
Area
{
get
{
return
area; }
set
{ area = value; }
}
// The event. Note that by using the generic EventHandler<T> event type
// we do not need to declare a separate delegate type.
public
event
EventHandler<ShapeEventArgs> ShapeChanged;
public
abstract
void
Draw();
//The event-invoking method that derived classes can override.
protected
virtual
void
OnShapeChanged(ShapeEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<ShapeEventArgs> handler = ShapeChanged;
if
(handler !=
null
)
{
handler(
this
, e);
}
}
}
public
class
Circle : Shape
{
private
double
radius;
public
Circle(
double
d)
{
radius = d;
area = 3.14 * radius * radius;
}
public
void
Update(
double
d)
{
radius = d;
area = 3.14 * radius * radius;
OnShapeChanged(
new
ShapeEventArgs(area));
}
protected
override
void
OnShapeChanged(ShapeEventArgs e)
{
// Do any circle-specific processing here.
// Call the base class event invocation method.
base
.OnShapeChanged(e);
}
public
override
void
Draw()
{
Console.WriteLine(
"Drawing a circle"
);
}
}
public
class
Rectangle : Shape
{
private
double
length;
private
double
width;
public
Rectangle(
double
length,
double
width)
{
this
.length = length;
this
.width = width;
area = length * width;
}
public
void
Update(
double
length,
double
width)
{
this
.length = length;
this
.width = width;
area = length * width;
OnShapeChanged(
new
ShapeEventArgs(area));
}
protected
override
void
OnShapeChanged(ShapeEventArgs e)
{
// Do any rectangle-specific processing here.
// Call the base class event invocation method.
base
.OnShapeChanged(e);
}
public
override
void
Draw()
{
Console.WriteLine(
"Drawing a rectangle"
);
}
}
// Represents the surface on which the shapes are drawn
// Subscribes to shape events so that it knows
// when to redraw a shape.
public
class
ShapeContainer
{
List<Shape> _list;
public
ShapeContainer()
{
_list =
new
List<Shape>();
}
public
void
AddShape(Shape s)
{
_list.Add(s);
// Subscribe to the base class event.
s.ShapeChanged += HandleShapeChanged;
}
// ...Other methods to draw, resize, etc.
private
void
HandleShapeChanged(
object
sender, ShapeEventArgs e)
{
Shape s = (Shape)sender;
// Diagnostic message for demonstration purposes.
Console.WriteLine(
"Received event. Shape area is now {0}"
, e.NewArea);
// Redraw the shape here.
s.Draw();
}
}
class
Test
{
static
void
Main(
string
[] args)
{
//Create the event publishers and subscriber
Circle c1 =
new
Circle(54);
Rectangle r1 =
new
Rectangle(12, 9);
ShapeContainer sc =
new
ShapeContainer();
// Add the shapes to the container.
sc.AddShape(c1);
sc.AddShape(r1);
// Cause some events to be raised.
c1.Update(57);
r1.Update(7, 7);
// Keep the console window open in debug mode.
System.Console.WriteLine(
"Press any key to exit."
);
System.Console.ReadKey();
}
}
}
輸出:
Received event. Shape area is now 10201.86
Drawing a circle
Received event. Shape area is now 49
Drawing a rectangle
實現接口事件
接口可聲明事件。下面的示例演示如何在類中實現接口事件。實現接口事件的規則與實現任何接口方法或屬性的規則基本相同。
在類中實現接口事件
在類中聲明事件,然後在適當的區域調用該事件。
namespace
ImplementInterfaceEvents
{
public
interface
IDrawingObject
{
event
EventHandler ShapeChanged;
}
public
class
MyEventArgs : EventArgs
{
// class members
}
public
class
Shape : IDrawingObject
{
public
event
EventHandler ShapeChanged;
void
ChangeShape()
{
// Do something here before the event…
OnShapeChanged(
new
MyEventArgs(
/*arguments*/
));
// or do something here after the event.
}
protected
virtual
void
OnShapeChanged(MyEventArgs e)
{
if
(ShapeChanged !=
null
)
{
ShapeChanged(
this
, e);
}
}
}
}
下面的示例演示如何處理以下的不常見情況:您的類是從兩個以上的接口繼承的,每個接口都含有同名事件)。在這種情況下,您至少要為其中一個事件提供顯式接口實現。為事件編寫顯式接口實現時,必須編寫 add 和 remove 事件訪問器。這兩個事件訪問器通常由編譯器提供,但在這種情況下編譯器不能提供。
您可以提供自己的訪問器,以便指定這兩個事件是由您的類中的同一事件表示,還是由不同事件表示。例如,根據接口規范,如果事件應在不同時間引發,則可以將每個事件與類中的一個單獨實現關聯。在下面的示例中,訂戶將形狀引用強制轉換為 IShape 或 IDrawingObject,從而確定自己將會接收哪個 OnDraw 事件。
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();
}
}
}
輸出:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Sub2 receives the IShape event.