Notes Chapter 11 Events

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.

Keywords: C#

Added by hubfub on Wed, 12 Jun 2019 00:14:42 +0300