#395 – Overriding the Default add and remove Accessors for an Event

When you use the event keyword to define an event, the compiler automatically creates a private backing variable that is a delegate instance, as well as code that encapsulates adding and removing methods to the delegate’s invocation list.

        public event EventHandler Barked;


You can also explicitly declare the backing variable and the add/remove accessors.  The following code results in equivalent behavior to the code shown above.

        private EventHandler barked;
        public event EventHandler Barked
        {
            add { barked += value; }
            remove { barked -= value; }
        }

To the client code, this looks the same–we just have a Barked event. But you could now add some custom code, e.g. logging.

        private EventHandler barked;
        public event EventHandler Barked
        {
            add
            {
                barked += value;
                Logger.Log(string.Format("Added handler {0} ({1})", value.Method.Name, value.Method.ReflectedType));
            }

            remove
            {
                barked -= value;
                Logger.Log(string.Format("Removed handler {0} ({1})", value.Method.Name, value.Method.ReflectedType));
            }
        }
Advertisements

#394 – Raising an Event in a Thread-Safe Manner

When you raise an event, you typically check to make sure that the event is not null, i.e. that at least one handler exists.   This method could result in a null reference exception if another thread removes all handlers after we check for null, but before we raise the event.

A more thread-safe approach is to make a copy of the event before raising it.  If a different thread removes all handlers from the original event, the copy is unaffected.

        public event EventHandler Barked;
        protected virtual void OnBarked()
        {
            EventHandler eventCopy = Barked;

            if (eventCopy != null)
                eventCopy(this, null);
        }

A cleaner approach, also thread-safe, is to declare the event with a null handler.  We can then avoid checking for null, since there will always be at least one handler.

        public event EventHandler Barked = delegate { };

        protected virtual void OnBarked()
        {
            Barked(this, null);
        }

#393 – Implement a Helper Method to Raise an Event

You typically raise an event from within a class by first checking to see if the event is null, i.e. if there are any handlers defined for the event.

        public void Bark(string barkSound)
        {
            Console.WriteLine("{0} says {1}", Name, barkSound);

            if (Barked != null)
                Barked(this, new BarkEventArgs(Name, barkSound));
        }

It’s common practice, however, to define a helper method in your class that actually raises the event.  This method encapsulates the logic of raising the event, including the check for null.  If you also define this method as protected virtual, future subclasses of your class can override the helper method to override the behavior of raising the event.

        public void Bark(string barkSound)
        {
            Console.WriteLine("{0} says {1}", Name, barkSound);
            OnBarked(barkSound);
        }

        protected virtual void OnBarked(string barkSound)
        {
            if (Barked != null)
                Barked(this, new BarkEventArgs(Name, barkSound));
        }

#390 – Using the Same Handler for Multiple Events

You can attach a single event handler (method) to more than one event, originating from more than one object.

In the example below, we have a general DogMadeSound handler that we attach to the Barked and Growled events of one Dog instance (Kirby) and to the Barked event of another Dog (Jack).  When any of these events fire, our handler will get called.

        static void Main()
        {
            Dog kirby = new Dog("Kirby");
            Dog jack = new Dog("Jack");

            // We handle Kirby's barks and growls and Jack's barks
            kirby.Barked += new EventHandler<DogEventArgs>(DogMadeSound);
            kirby.Growled += new EventHandler<DogEventArgs>(DogMadeSound);
            jack.Barked += new EventHandler<DogEventArgs>(DogMadeSound);

            kirby.Bark("Woof");
            kirby.Growl();
            jack.Bark("Yap");
        }

        static void DogMadeSound(object sender, DogEventArgs e)
        {
            Console.WriteLine("DogMadeSound handler: {0} - {1}", e.DogName, e.Sound);
        }

#389 – Check for Null Before Raising an Event

You should always check to see if an event is null, before trying to raise it.

            if (Barked != null)
                Barked(this, new SomebodyBarkedEventArgs(Name, barkSound));

When no handlers are attached to the event, it will have a null value. If you try raising the event when it is null, you’ll get a NullReferenceException.

Alternatively, you could avoid having to check for null if you attach an empty handler to the event when you declare it.

        public event EventHandler<SomebodyBarkedEventArgs> Barked = delegate { };

#388 – Declaring and Using Private Events

You can declare private events in a class and they can be either instance or static events.  A private event is one that code within the class can subscribe to, but that is not visible to code outside the class.

For example, we can create a private static event in the Dog class that will fire whenever a new dog is created.

        private static event EventHandler<DogCreatedEventArgs> DogCreated;

This event will be visible only to code within the Dog class.

In the static constructor, we attach a handler that will record each Dog created.

        static Dog()
        {
            Dog.DogCreated += new EventHandler<DogCreatedEventArgs>(Dog_DogCreated);
        }

        static List<string> DogList = new List<string>();
        static void Dog_DogCreated(object sender, DogCreatedEventArgs e)
        {
            DogList.Add(e.Name);
        }

Finally, we fire the event from the instance constructor.

        public Dog(string name)
        {
            Name = name;

            if (Dog.DogCreated != null)
                Dog.DogCreated(this, new DogCreatedEventArgs(name));
        }

#387 – Static Events vs. Static Event Handler Methods

Events can be static events or instance events.  Static events are associated with the class as a whole, whereas instance events are associated with a specific instance of the class.

You can attach event handlers to both static and instance events.

            // Add handler to static event
            Dog.SomebodyBarked += new EventHandler<SomebodyBarkedEventArgs>(Dog_SomebodyBarked);

            Dog kirby = new Dog("Kirby");

            // Add handler to instance event
            kirby.Barked += new EventHandler<SomebodyBarkedEventArgs>(kirby_Barked);

In both cases, we’re attaching a handler that is a static method.

        static void Dog_SomebodyBarked(object sender, SomebodyBarkedEventArgs e)
        {
            Console.WriteLine("Dog {0} barked, saying {1}", e.DogName, e.Bark);
        }

We can also attach a handler that is an instance method.  So we can attach both static and instance methods as handlers, to either static or instance events.

For example:

            BarkLogger log = new BarkLogger();

            // Add instance method as handler for static event
            Dog.SomebodyBarked += new EventHandler<SomebodyBarkedEventArgs>(log.LogBark);