C# example for designing a Type that exposes an Event which is raised in a thread-safe manner.
// ===========================================================================
// Example from CLR via C# 4th Edition. By Jeffrey Richter
// Chapter 11: Events - Designing a Type That Exposes an Event, Raising an Event in a Thread-Safe Way
// https://www.microsoftpressstore.com/store/clr-via-c-sharp-9780735667457
// ===========================================================================
/// <summary>
/// Step #1: Define a type that will hold any additional information that
/// should be sent to receivers of the event notification.
/// </summary>
internal class NewMailEventArgs : EventArgs
{
private readonly String m_from, m_to, m_subject;
public NewMailEventArgs(String from, String to, String subject)
{
m_from = from; m_to = to; m_subject = subject;
}
public String From { get { return m_from; } }
public String To { get { return m_to; } }
public String Subject { get { return m_subject; } }
}
public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e);
internal class MailManager
{
/// <summary>
/// Step #2: Define the event member
///
/// NewMail is the name of this event. The type of the event member is
/// EventHandler<New MailEventArgs>, which means that all receivers of the
/// event notification must supply a callback method whose prototype matches
/// that of the EventHandler<NewMailEventArgs> delegate type. Because the
/// generic System.EventHandler delegate is defined as follows:
///
/// public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e);
///
/// the method prototypes must look like the following:
///
/// void MethodName(Object sender, NewMailEventArgs e);
/// </summary>
public event EventHandler<NewMailEventArgs> NewMail;
/// <summary>
/// Step 3: Raising an Event in a Thread-Safe Way
///
/// Define a method responsible for raising the event to notify registered
/// objects that the event has occurred If this class is sealed, make
/// this method private and non-virtual.
///
/// The call to Volatile.Read forces NewMail to be read at the point of
/// the call and the reference really has to be copied to the temp
/// variable now. Then, temp will be invoked only if it is not null.
/// </summary>
protected virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);
if (temp != null)
{
temp(this, e);
}
}
/// <summary>
/// Step #4: Define a method that translates the input into the desired event.
///
/// Your class must have some method that takes some input and translates
/// it into the raising of the event. In my MailManager example,
/// the SimulateNewMail method is called to indicate that a new email
/// message has arrived into MailManager.
/// </summary>
public void SimulateNewMail(String from, String to, String subject)
{
// Construct an object to hold the information we want
// to pass to the receivers of our notification
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);
// Call our virtual method notifying our object that the event
// occurred. If no type overrides this method, our object will
// notify all the objects that registered interest in the event
OnNewMail(e);
}
}
/// <summary>
/// As a convenience, you could define an extension method (as discussed in
/// Chapter 8, "Methods") that encapsulates this thread-safety logic.
/// Define the extension method as follows.
/// </summary>
public static class EventArgExtensions
{
public static void Raise<TEventArgs>(this TEventArgs e, Object sender, ref EventHandler<TEventArgs> eventDelegate)
{
// Copy a reference to the delegate field now into a temporary field for thread safety
EventHandler<TEventArgs> temp = Volatile.Read(ref eventDelegate);
// If any methods registered interest with our event, notify them
if (temp != null)
{
temp(sender, e);
}
}
}
// And now, we can rewrite the OnNewMail method as follows.
protected virtual void OnNewMail(NewMailEventArgs e)
{
e.Raise(this, ref m_NewMail);
}