You can declare an event on any class or struct using the following syntax:
publicclassMyClass{// Declares the event for MyClasspubliceventEventHandler MyEvent;// Raises the MyEvent eventpublicvoidRaiseEvent(){OnMyEvent();}}
There is an expanded syntax for declaring events, where you hold a private instance of
the event, and define a public instance using add and set accessors. The syntax is very similar to C# properties. In all cases, the syntax demonstrated above should be preferred, because the compiler emits code to help ensure that multiple threads can safely add and remove event handlers to the event on your class.
privatevoidOnMyEvent(){// Use a local for EventName, because another thread can modify the// public EventName between when we check it for null, and when we// raise the event.var eventName = EventName;// If eventName == null, then it means there are no event-subscribers,// and therefore, we cannot raise the event.if(eventName !=null)eventName(this, EventArgs.Empty);}
Note that events can only be raised by the declaring type. Clients can only subscribe/unsubscribe.
For C# versions before 6.0, where EventName?.Invoke is not supported, it is a good practice to assign the event to a temporary variable before invocation, as shown in the example, which ensures thread-safety in cases where multiple threads execute the same code. Failing to do so may cause a NullReferenceException to be thrown in certain cases where multiple threads are using the same object instance. In C# 6.0, the compiler emits code similar to that shown in the code example for C# 6.
Create an event using EventHandler<T> and use the new cancel event arg class which you created.
Example
In the below example, we create a PriceChangingEventArgs event for Price property of a class. The event data class contains a Value which let the consumer know about the new . The event raises when you assign a new value to Price property and lets the consumer know the value is changing and let them to cancel the event. If the consumer cancels the event, the previous value for Price will be used:
PriceChangingEventArgs
publicclassPriceChangingEventArgs:CancelEventArgs{intvalue;publicint Value
{get{returnvalue;}}publicPriceChangingEventArgs(intvalue){this.value=value;}}
If a class raises a large the number of events, the storage cost of one field per delegate may not be acceptable. The .NET Framework provides event properties(opens new window) for these cases. This way you can use another data structure like EventHandlerList(opens new window) to store event delegates:
publicclassSampleClass{// Define the delegate collection.protectedEventHandlerList eventDelegates =newEventHandlerList();// Define a unique key for each event.staticreadonlyobject someEventKey =newobject();// Define the SomeEvent event property.publiceventEventHandler SomeEvent
{add{// Add the input delegate to the collection.
eventDelegates.AddHandler(someEventKey,value);}remove{// Remove the input delegate from the collection.
eventDelegates.RemoveHandler(someEventKey,value);}}// Raise the event with the delegate specified by someEventKeyprotectedvoidOnSomeEvent(EventArgs e){var handler =(EventHandler)eventDelegates[someEventKey];if(handler !=null)handler(this, e);}}
This approach is widely used in GUI frameworks like WinForms where controls can have dozens and even hundreds of events.
Note that EventHandlerList is not thread-safe, so if you expect your class to be used from multiple threads, you will need to add lock statements or other synchronization mechanism (or use a storage that provides thread safety).
Declaration & subscription of an event handler that does not use the event's parameter, and so can use the above syntax without needing to specify parameters:
EventName +=delegate{/* Handler Logic */}
Invoking the event:
EventName?.Invoke(SenderObject, EventArguments);
Non-Standard Event Declaration
Events can be of any delegate type, not just EventHandler and EventHandler<T>. For example:
//Declaring an eventpubliceventAction<Param1Type, Param2Type,...> EventName;
This is used similarly to standard EventHandler events:
//Adding a named event handlerpublicvoidHandlerName(Param1Type parameter1,Param2Type parameter2,...){/* Handler logic */}
EventName += HandlerName;//Adding an anonymous event handler
EventName +=(parameter1, parameter2,...)=>{/* Handler Logic */};//Invoking the eventEventName(parameter1, parameter2,...);
It is possible to declare multiple events of the same type in a single statement, similar to with fields and local variables (though this may often be a bad idea):
publiceventEventHandler Event1, Event2, Event3;
This declares three separate events (Event1, Event2, and Event3) all of type EventHandler. Note: Although some compilers may accept this syntax in interfaces as well as classes, the C# specification (v5.0 ยง13.2.3) provides grammar for interfaces that does not allow it, so using this in interfaces may be unreliable with different compilers.
Creating custom EventArgs containing additional data
Custom events usually need custom event arguments containing information about the event. For example MouseEventArgs(opens new window) which is used by mouse events like MouseDown or MouseUp events, contains information about Location or Buttons which used to generate the event.
When creating new events, to create a custom event arg:
As a convention, the name of the class should ends with EventArgs.
Example
In the below example, we create a PriceChangingEventArgs event for Price property of a class. The event data class contains a CurrentPrice and a NewPrice. The event raises when you assign a new value to Price property and lets the consumer know the value is changing and let them to know about current price and new price:
You can enhance the example by allowing the consumer to change the new value and then the value will be used for property. To do so it's enough to apply these changes in classes.
Change the definition of NewPrice to be settable:
publicint NewPrice {get;set;}
Change the definition of Price to use e.NewPrice as value of property, after calling OnPriceChanging :
int price;publicint Price
{get{return price;}set{var e =newPriceChangingEventArgs(price,value);OnPriceChanging(e);
price = e.NewPrice;}}
Parameters
Parameter
Details
EventArgsT
The type that derives from EventArgs and contains the event parameters.
EventName
The name of the event.
HandlerName
The name of the event handler.
SenderObject
The object that's invoking the event.
EventArguments
An instance of the EventArgsT type that contains the event parameters.
Remarks
When raising an event:
Always check if the delegate is null. A null delegate means the event has no subscribers. Raising an event with no subscribers will result in a NullReferenceException.
Copy the delegate (e.g. EventName) to a local variable (e.g. eventName) before checking for null / raising the event. This avoids race conditions in multi-threaded environments:
Wrong:
if(Changed != null) // Changed has 1 subscriber at this point
// In another thread, that one subscriber decided to unsubscribe
Changed(this, args); // `Changed` is now null, `NullReferenceException` is thrown.
Right:
// Cache the "Changed" event as a local. If it is not null, then use
// the LOCAL variable (handler) to raise the event, NOT the event itself.
var handler = Changed;
if(handler != null)
handler(this, args);
Use the null-conditional operator (?.) for raising the method instead of null-checking the delegate for subscribers in an if statement: EventName?.Invoke(SenderObject, new EventArgsT());
When using Action<> to declare delegate types, the anonymous method / event handler signature must be the same as the declared anonymous delegate type in the event declaration.