Events permit a class to declare notifications for which clients can attach executable code in the form of event handlers. Events are declared using event-declarations:
An event declaration is either an event-field-declaration or an event-property-declaration. In both cases, the declaration may include set of attributes (§17), a new
modifier (§10.2.2), a valid combination of the four access modifiers (§10.2.3), and a static
modifier (§10.2.5).
The type of an event declaration must be a delegate-type (§15), and that delegate-type must be at least as accessible as the event itself (§3.3.4).
An event field declaration corresponds to a field-declaration (§10.4) that declares one or more fields of a delegate type. The readonly
modifier is not permitted in an event field declaration.
An event property declaration corresponds to a property-declaration (§10.6) that declares a property of a delegate type. The member-name and accessor-declarations are equivalent to those of a property declaration, except that an event property declaration must include both a get
accessor and a set
accessor, and that the accessors are not permitted to include virtual
, override
, or abstract
modifiers.
Within the program text of the class or struct that contains an event member declaration, the event member corresponds exactly to a private field or property of a delegate type, and the member can thus be used in any context that permits a field or property.
Outside the program text of the class or struct that contains an event member declaration, the event member can only be used as the left hand operand of the +=
and -=
operators (§7.13.3). These operators are used to attach or remove event handlers to or from an event member, and the access modifiers of the event member control the contexts in which the operations are permitted.
Since +=
and -=
are the only operations that are permitted on an event member outside the type that declares the event member, external code can append and remove handlers for an event, but cannot in any other way obtain or modify the value of the underlying event field or event property.
In the example
public delegate void EventHandler(object sender, Event e); public class Button: Control { public event EventHandler Click; protected void OnClick(Event e) { if (Click != null) Click(this, e); } public void Reset() { Click = null; } }
there are no restrictions on usage of the Click
event field within the Button
class. As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. The OnClick
method in the Button
class "raises" the Click
event. The notion of raising an event is precisely equivalent to invoking the delegate represented by the event member—thus, there are no special language constructs for raising events. Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.
Outside the declaration of the Button
class, the Click
member can only be used on the left hand side of the +=
and -=
operators, as in
b.Click += new EventHandler(...);
which appends a delegate to the invocation list of the Click
event, and
b.Click -= new EventHandler(...);
which removes a delegate from the invocation list of the Click
event.
In an operation of the form x
+=
y
or x
-=
y
, when x
is an event member and the reference takes place outside the type that contains the declaration of x
, the result of the operation is void
(as opposed to the value of x
after the assignment). This rule prohibits external code from indirectly examining the underlying delegate of an event member.
The following example shows how event handlers are attached to instances of the Button
class above:
public class LoginDialog: Form { Button OkButton; Button CancelButton; public LoginDialog() { OkButton = new Button(...); OkButton.Click += new EventHandler(OkButtonClick); CancelButton = new Button(...); CancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, Event e) { // Handle OkButton.Click event } void CancelButtonClick(object sender, Event e) { // Handle CancelButton.Click event } }
Here, the LoginDialog
constructor creates two Button
instances and attaches event handlers to the Click
events.
Event members are typically fields, as in the Button
example above. In cases where the storage cost of one field per event is not acceptable, a class can declare event properties instead of event fields and use a private mechanism for storing the underlying delegates. (In scenarios where most events are unhandled, using a field per event may not be acceptable. The ability to use a properties rather than fields allows for space vs. speed tradeoffs to be made by the developer.)
In the example
class Control: Component { // Unique keys for events static readonly object mouseDownEventKey = new object(); static readonly object mouseUpEventKey = new object(); // Return event handler associated with key protected Delegate GetEventHandler(object key) {...} // Set event handler associated with key protected void SetEventHandler(object key, Delegate handler) {...} // MouseDown event property public event MouseEventHandler MouseDown { get { return (MouseEventHandler)GetEventHandler(mouseDownEventKey); } set { SetEventHandler(mouseDownEventKey, value); } } // MouseUp event property public event MouseEventHandler MouseUp { get { return (MouseEventHandler)GetEventHandler(mouseUpEventKey); } set { SetEventHandler(mouseUpEventKey, value); } } }
the Control
class implements an internal storage mechanism for events. The SetEventHandler
method associates a delegate value with a key, and the GetEventHandler
method returns the delegate currently associated with a key. Presumably the underlying storage mechanism is designed such that there is no cost for associating a null
delegate value with a key, and thus unhandled events consume no storage.