logo search
CSharp_Prog_Guide

Создание событий базового класса в производных классах

В следующем примере показан стандартный способ объявления событий в базовом классе таким образом, чтобы они могли создаваться и из производного класса. Этот принцип широко используется в классах Windows Forms в библиотеке классов .NET Framework.

При создании класса, который может служить базовым для других классов, следует учитывать, что события являются делегатами особого типа, которые могут быть вызваны только из класса, который их объявил. Производные классы не могут напрямую создавать события, объявленные в базовом классе. Иногда нужно, чтобы событие могло создаваться только в базовом классе, однако чаще всего следует обеспечить производному классу возможность создания событий базового класса. Для этого следует создать в базовом защищенный метод, предоставляющий оболочку для события. Путем вызова или переопределения этого метода производные классы могут опосредованно вызывать событие.

Примечание.

Не следует объявлять виртуальные события в базовом классе и переопределять их в производном классе. Компилятор C# неправильно обрабатывает такие события в Microsoft Visual Studio 2008, невозможно с точностью предсказать, будет ли подписчик производного события в действительности подписан на событие базового класса.

Пример

--------

// 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.

Console.WriteLine();

Console.WriteLine("Press Enter to exit");

Console.ReadLine();

}

}

}

Received event. Shape area is now 178.98

Drawing a circle

Received event. Shape area is now 49

Drawing a rectangle

-----

How to: Raise and Consume Events

The following example program demonstrates raising an event in one class and handling the event in another class. The AlarmClock class defines the public event Alarm, and provides methods to raise the event. The AlarmEventArgs class derives from EventArgs and defines the data specific to an Alarm event. The WakeMeUp class defines the method AlarmRang, which handles an Alarm event. The AlarmDriver class uses the classes together, setting the AlarmRang method of WakeMeUp to handle the Alarm event of the AlarmClock.

Example

// EventSample.cs.

//

namespace EventSample

{

using System;

using System.ComponentModel;

// Class that contains the data for

// the alarm event. Derives from System.EventArgs.

//

public class AlarmEventArgs : EventArgs

{

private readonly bool snoozePressed ;

private readonly int nrings;

//Constructor.

//

public AlarmEventArgs(bool snoozePressed, int nrings)

{

this.snoozePressed = snoozePressed;

this.nrings = nrings;

}

// The NumRings property returns the number of rings

// that the alarm clock has sounded when the alarm event

// is generated.

//

public int NumRings

{

get { return nrings;}

}