More about the event class


In Popular patterns around events?, several folks mentioned that a usage example would be a good idea.  As I put one together, I found some small changes to make to the code.  Just goes to show you that thinking about your consumer is a good idea.


 


Since this is a class for people writing classes, there are actually two consumers to consider: the user of my event class, and the user of the class you’re writing.


 


My sample code is based on you writing a custom control.  You want it to fire an event before & after painting.  A consumer should be able to write:


 



      MyControl myControl = new MyControl();


      myControl.PaintEvents.Before += delegate { Console.WriteLine(“painting has begun”); };


      myControl.PaintEvents.After += delegate { Console.WriteLine(“painting has completed”); };


 


      myControl.Invalidate();


 


I expect Invalidate to cause a paint to happen.


 


Here’s the code for the control:


 



      class MyControl


      {


            readonly MyEvent<EventArgs> _paintEvents;


            public IRestrictedMyEvent<EventArgs> PaintEvents { get { return _paintEvents; } }


 


            public MyControl()


            {


                  this._paintEvents = new MyEvent<EventArgs>(this);


            }


 


            public void Invalidate()


            {


                  using (_paintEvents.Open(new EventArgs()))


                  {


                        // do your painting here


                  }


            }


      }


 


As mentioned, I modified the previous code a little.  So, here’s the updated version:


 



      // IMO, this belongs as a nested type in MyEvent, called IRestrictedView.


      // C# doesn’t support inheriting from nested types, doh!


      interface IRestrictedMyEvent<T> where T : EventArgs


      {


            // MS guidelines say “Consider naming events with a verb. “


            //


            // I don’t see how to follow that here.  Perhaps “Raising” and


            // “Raised”?


            event MyEvent<T>.Handler Before;


            event MyEvent<T>.Handler After;


      }


 


      // nested type


      partial class MyEvent<T> where T : EventArgs


      {


            // MS guidelines say “Use an EventHandler suffix on event handler names.”


            //


            // The full name of this type is “MyEvent.Handler”, which seems to match


            // the guidance.


            public delegate void Handler(object sender, T e);


 


      }


 


      // IRestrictedMyEvent implementation


      partial class MyEvent<T> : IRestrictedMyEvent<T>


      {


            public event Handler Before;


            public event Handler After = delegate { };


      }


 


      // Overridable methods to fire the events. 


      // It’s not clear to me how these are useful, but people have asked for them


      partial class MyEvent<T>


      {


            protected virtual void OnBefore(T e)


            {


                  this.Before(this._sender, e);


            }


 


            protected virtual void OnAfter(T e)


            {


                  this.After(this._sender, e);


            }


      }


 


      // relationship with owner, so I can pass the right sender


      partial class MyEvent<T>


      {


            object _sender;


 


            public MyEvent(object sender)


            {


                  this._sender = sender;


            }


      }


 


      // Public API


      partial class MyEvent<T>


      {


            T _e;


 


            public IDisposable Open(T e)


            {


                  this._e = e;


                  this.OnBefore(e);


 


                  return this;


            }


 


            public void Close()


            {


                  this.OnAfter(_e);


                  this.DebugOnClose();


            }


      }


 


      // IDisposable


      partial class MyEvent<T> : IDisposable


      {


            void IDisposable.Dispose()


            {


                  this.Close();


            }


 


            // add this if you need to call Dispose on an instance of MyEvent


            //public IDisposable IDisposable { get { return this; } }


      }


 


      // DEBUG verification


      //


      // In DEBUG builds, make sure that Dispose/Close really is called.


      // If you don’t, an assert will fire when the GC runs.


      partial class MyEvent<T>


      {


            [Conditional(“DEBUG”)]


            void DebugOnClose()


            {


                  GC.SuppressFinalize(this);


 


            }


 


            //  You can’t put a Conditional attribute on a destructor!


            //    [Conditional(“DEBUG”)]


#if DEBUG


            ~MyEvent()


            {


                  Debug.Fail(“MyEvent was not properly disposed”);


            }


#endif


      }