11.1 Types of Design Disclosure Events
11.1.1 Step 1: Define types to accommodate all additional information that needs to be sent to event notification recipients
//Step 1: Define a type to hold all additional information that should be sent to the event notification recipient 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; } } }
11.1.2 Step 2: Define Event Members
internal class MailManager { //Step 2: Define event members public event EventHandler<NewMailEventArgs> NewMail; ... }
The method prototype must have the following forms:
void MethodName(object sender,NewMailEventArgs e);
- The return type of all event handlers is required to be void because several callback methods may be invoked after the event is raised, but the return values of all methods cannot be obtained.
11.1.3 Step 3: Define the method responsible for triggering an event to notify the registered object of the event
internal class MailManager { ... //Step 3: Define the method responsible for triggering the event to notify the logged-in object. //If the class is sealed, the method is declared private and non-virtual protected virtual void OnNewMail(NewMailEventArgs e) { Volatile.Read(ref NewMail)?.Invoke(this, e); } ... }
It might have been written like this before:
protected virtual void OnNewMail(NewMailEventArgs e){ //For thread safety reasons, the reference to the delegate field is now copied into a temporary variable EventHandler<NewMailEventArgs> temp = NewMail; if (temp != null) temp(this,e); }
What's wrong with this is that temp may be optimized by the compiler (all JIT compilers in MS do not do this at present, but this is theoretically possible), so it can't prevent other threads from removing delegates before calling NewMail, leading to the problem that NewMail becomes null. So you can write as follows:
EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail); if (temp != null) temp(this, e);
The call to Volatile.Read forces NewMail to read when the call occurs, and references really have to be copied into temp variables (compilers don't want to take shortcuts).
Volatile.Read(ref NewMail)?.Invoke(this, e) is c#6.0.
11.1.4 Step 4: Define a method to translate input into expected events
internal class MailManager { ... //Step 4: Define a way to translate input into expected events public void SimulateNewMail(string from, string to, string subject) { //Construct an object to hold the information you want to pass to the notification recipient NewMailEventArgs e = new NewMailEventArgs(from, to, subject); //Calling a virtual method notifies the object that an event has occurred. //If there is no type override of this method, our object will notify all registered objects of the event OnNewMail(e); } ... }
11.2 How does the compiler implement events
The c compiler converts it to the following three constructs at compile time:
// 1. A private delegate field initialized as null private EventHandler<NewMailEventArgs> NewMail = null; // 2. A common add_Xxx method (where Xxx is the event name) // Allowing method logins to focus on events public void add_NewMail(EventHandler<NewMailEventArgs> value) { // Through loops and calls to CompareExchange, you can // Adding delegates to events in a thread-safe manner EventHandler<NewMailEventArgs> prevHandler; EventHandler<NewMailEventArgs> newMail = this.NewMail; do { prevHandler = newMail; //Merge the original delegation with the newly added delegation EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Combine(prevHandler, value); //If prevHandler is equal to this.NewMail (that is, during the current thread addition delegation process, //No other thread adds delegates to this.NewMail) //Update this.NewMail to the merged new delegate newHandler //newMail is the original value of this.NewMail newMail = Interlocked.CompareExchange(ref this.NewMail, newHandler, prevHandler); //Otherwise, perform the next cycle and rerun the merge operation } while (newMail != prevHandler); } // 3. A common remove_Xxx method (where Xxx is the event name) // Allow methods to deregister attention to events public void remove_NewMail(EventHandler<NewMailEventArgs> value) { // Through loops and calls to CompareExchange, you can // Remove a delegate from an event in a thread-safe manner EventHandler<NewMailEventArgs> prevHandler; EventHandler<NewMailEventArgs> newMail = this.NewMail; do { prevHandler = newMail; EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>)Delegate.Remove(prevHandler, value); newMail = Interlocked.CompareExchange(ref this.NewMail, newHandler, prevHandler); } while (newMail != prevHandler); }
- In an attempt to delete methods that have never been added, Delegate's Remoove method does nothing internally. That is to say, no exceptions are thrown and no warnings are displayed.
11.3 Design the types of listening events
internal sealed class Fax { //Pass the MailManager object to the constructor public Fax(MailManager mm) { //Construct an instance of EventHandler < NewMail EventArgs > delegation. //Make it refer to our FaxMsg callback method //Register our callback method with MailManager's NewMail event mm.NewMail += FaxMsg; } //MailManager will call this method when a new e-mail arrives private void FaxMsg(object sender, NewMailEventArgs e) { // 'sender'denotes the MailManager object to facilitate the return of information to it // 'e'indicates additional event information that the MailManager object wants to pass to us Console.WriteLine("Faxing mail message:"); Console.WriteLine(" From={0},To={1},Subject={2}", e.From, e.To, e.Subject); } //Executing this method, the Fax object will cancel its attention to the NewMail event. //No further notification will be received public void Unregister(MailManager mm) { //Write off your attention to MailManager's New Mail event mm.NewMail -= FaxMsg; } }
- += Operator adds delegates to events; -= operation logs out delegates to events (scan delegate list, find an appropriate delegate - the same method of encapsulation and delivery in the base, and then remove).
- When an object no longer wants to receive event notification, it should cancel its attention to the event. As long as the object registers one of its methods to the event, it cannot be garbage collected. Therefore, if your type implements the Disose method of IDisposable, it should cancel the attention to all events in the implementation.
11.4 Explicit Implementation Events
The author's code is good, but I don't think I'll ever encounter a situation where dozens of events are defined in a class, so I won't write it, so I'll turn it over when I need it.