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

      }