Compartir a través de


Using Events (C# Programming Guide) 

Events are used on classes and structs to notify the users of an object when something interesting happens in relation to that object. This notification is called raising an event. An object raising an event is referred to as the source or sender of the event. Objects can raise events for a variety of reasons: in response to a change in the object data, the completion of a long-running process, or an interruption in service, for example, an object using network resources can raise an event if network connectivity is lost. Objects that represent user interface elements usually raise events in response to user actions like a button click or a menu selection.

Declaring Events

Events, like methods, have a signature that includes a name and a parameter list. This signature is defined by a delegate type, for example:

public delegate void TestEventDelegate(object sender, System.EventArgs e);

It is very common for events in the .NET Framework to have a signature where the first parameter is an object referring to the source of the event, and the second parameter is a class carrying data related to the event. However, this design is not required or enforced by the C# language; an event signature can be the same as any valid delegate signature, as long as it returns void.

To add an event to a class requires using the event keyword, and providing a delegate type and a name for the event. For example:

public class EventSource
{
    public event TestEventDelegate TestEvent;
    private void RaiseTestEvent() { /* ... */ }
}

Events can be marked as public, private, protected, internal, or protected internal. These access modifiers define how users of the class can access the event. For more information, see Access Modifiers.

An event may be declared as a static event using the static keyword. This makes the event available to callers at any time, even if no instance of the class exists. For more information, see Static Classes and Static Class Members.

An event can be marked as a virtual event using the virtual keyword. This allows derived classes to override the event behavior using the override keyword. For more information, see Inheritance (C# Programming Guide). An event overriding a virtual event can also be sealed, specifying that for derived classes it is no longer virtual. Lastly, an event can be declared abstract, meaning there is no implementation in the class, and derived classes must write their own implementation. For more information, see Abstract and Sealed Classes and Class Members.

Raising Events

To raise an event, the class can call the delegate, passing any parameters related to the event. The delegate will then call all of the handlers that have been added to the event. When there are no handlers for the event, the event is null, so before raising an event, event sources should make sure the event is not null to avoid NullReferenceException. To avoid a race condition where the last handler can be removed between the null check and the invocation of the event, event sources should also create a copy of the event before performing the null check and raising the event. For example:

private void RaiseTestEvent()
{
    // Safely invoke an event:
    TestEventDelegate temp = TestEvent;

    if (temp != null)
    {
        temp(this, new System.EventArgs());
    }
}

Each event can have more than one handler assigned to receive the event. In this case, the event calls each receiver automatically; raising an event requires only one call to the event regardless of the number of receivers.

Subscribing to Events

A class that you want to receive an event can create a method to receive that event, and then add a delegate for that method to the class event itself. This process is called subscribing to an event.

First, there must be a method on the receiving class with the same signature as the event itself, such as the delegate signature. That method, called an event handler, can then take appropriate action in response to the event. For example:

public class EventReceiver
{
    public void ReceiveTestEvent(object sender, System.EventArgs e)
    {
        System.Console.Write("Event received from ");
        System.Console.WriteLine(sender.ToString());
    }
}

There can be more than one handler for each event. Multiple handlers are called sequentially by the source. If one handler raises an exception, the handlers that have not been called will not have the opportunity to receive the event. For this reason, it is recommend that event handlers handle the event quickly, and avoid raising exceptions.

To subscribe to an event, the receiver must create a delegate of the same type as the event, using the event handler as the delegate destination. Then the receiver must add that delegate to the event on the source object, using the addition assignment operator (+=). For example:

public void Subscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent += temp;
}

To unsubscribe from an event, the receiver can remove a delegate to the event handler from the event on the source object, using the subtraction assignment operator (-=). For example:

public void UnSubscribe(EventSource source)
{
    TestEventDelegate temp = new TestEventDelegate(ReceiveTestEvent);
    source.TestEvent -= temp;
}

Declaring Event Accessors

In the previous example, the event TestEvent was declared in a manner similar to fields. Like a field, the event is directly available to users of the object, who can modify it. Unlike fields, the only modifications allowed are through the addition assignment (+=) and subtraction assignment (-=) operators.

It is possible to declare an event using event accessors. Event accessors use a syntax very similar to property accessors, using an add keyword and a code block for adding event handlers to the event, and a remove keyword and a code block for removing event handlers to the event. For example:

public class EventSource2
{
    private TestEventDelegate TestEventHandlers;
    public event TestEventDelegate TestEvent
    {
        add
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers += value;
            }
        }
        remove
        {
            lock (TestEventHandlers)
            {
                TestEventHandlers -= value;
            }
        }
    }
    private void RaiseTestEvent()
    {
        // Safely invoke an event.
        TestEventDelegate temp = TestEventHandlers;

        if (temp != null)
        {
            temp(this, new System.EventArgs());
        }
    }
}

In order to use event accessors, the class raising the event must have a mechanism to store and retrieve handlers. The previous example uses a private delegate field, TestEventHandlers, and the addition and subtraction assignment operators to add and remove handlers from the list. This is very similar to how an event declared without accessors works. When an event receiver uses the addition assignment operator (+=) to add a handler to the event, the add accessor is called, and the new handler is available within the accessor as a local variable named value. When the subtraction assignment operator (-=) is used, the remove accessor is called, and the handler to be removed is available as a local variable named value. Both accessors return void, so any return statement must not return a value.

Subscribing and unsubscribing to an event uses the same syntax regardless of whether event accessors are declared by the class.

Note

The lock statement is used in the previous example to prevent more than one thread from manipulating the event list at the same time. For more information, see Lock Statements and Threading.

When accessors are used for an event, the class can store the event handlers in any way it chooses. Although the previous example uses a delegate, other mechanisms can be used. For an example, see How to: Use a Hash Table to Store Event Instances.

Events that do not declare accessors are given thread-safe accessors automatically by the C# compiler. Abstract events cannot declare accessors. Static accessors cannot use the this keyword.

See Also

Concepts

C# Programming Guide
Events (C# Programming Guide)
Delegates (C# Programming Guide)