Thread-safety is important and should be considered when designing event members on types that may be accessed concurrently by multiple threads.
Thread-safe Pattern
In the 2.0 Framework, the C# compiler adds a MethodImplAttribute with the MethodImplOptions.Synchronized flag to event members in class definitions that don't have explicitly defined add and remove accessors. The MethodImplOptions.Synchronized flag locks the current instance in each accessor. Some developers believe that this breaks encapsulation since the object can also be locked externally, possibly causing deadlocks. A better approach would be to encapsulate the locking mechanism completely by defining a private field on which to lock the event's accessors. It is recommended to lock a read-only instance of System.Object. By explicitly defining add and remove accessors, the event member can be synchronized using the private field and the C# compiler will omit the MethodImplAttribute completely:
private readonly object TextChangedEventLock = new object();
private EventHandler TextChangedEvent;
public event EventHandler TextChanged
{
add
{
lock (TextChangedEventLock)
{
TextChangedEvent += value;
}
}
remove
{
lock (TextChangedEventLock)
{
TextChangedEvent -= value;
}
}
}
The same mechanism used to synchronize the acccessors should also be used to synchronize the method that raises the event; however, care should be taken so that the event's delegate isn't invoked within the synchronization code, otherwise deadlocks can occur:
protected virtual void OnTextChanged(EventArgs e)
{
EventHandler handler = null;
lock (TextChangedEventLock)
{
handler = TextChangedEvent;
if (handler == null)
return;
}
// Invoke delegate outside of lock
handler(this, e);
}
Component Events
When declaring an event member on a type that derives from System.ComponentModel.Component, the inherited Event property provides an EventHandlerList that can be used to reduce the memory footprint of your class since you'll no longer have to declare a private field to back the event. Controls use this model since they commonly have many events defined but only a few are actually used at runtime:
private readonly object TextChangedEvent = new object();
public event EventHandler TextChanged
{
add
{
lock (TextChangedEvent)
{
Events.AddHandler(TextChangedEvent, value);
}
}
remove
{
lock (TextChangedEvent)
{
Events.RemoveHandler(TextChangedEvent, value);
}
}
}
protected virtual void OnTextChanged(EventArgs e)
{
EventHandler handler = null;
lock (TextChangedEvent)
{
handler = (EventHandler) Events[TextChangedEvent];
}
if (handler != null)
// Invoke delegate outside of lock
handler(this, e);
}