#1,164 – A Sealed Event Cannot Be Overridden

As with a method, you can use the sealed keyword when overriding a virtual event to indicate that child classes cannot override the event being defined.

When you override an event, you create a virtual event that behaves polymorphically.  The base class defines the event using the virtual keyword and you define the event in your child class using the override keyword.

When you use the override keyword, your event can itself be overridden in a child class.

If you want to override an event, but prevent any derived classes from overriding your implementation, you can use the sealed keyword to indicate that a child class must use the new keyword, rather than overriding your event.

    public class Dog
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        public virtual event EventHandler<string> Barked;
    }

    public class Terrier : Dog
    {
        public Terrier(string name) : base(name) { }

        private EventHandler<string> terrierBarked;
        public sealed override event EventHandler<string> Barked
        {
            add
            {
                terrierBarked += value;
                // Special Terrier.Barked event logic here
            }
            remove
            {
                terrierBarked -= value;
            }
        }
    }

    public class JackRussell : Terrier
    {
        public JackRussell(string name) : base(name) { }

        public new event EventHandler<string> Barked;
    }
Advertisement

#1,163 – An Abstract Event Has No Implementation

An abstract event is a special kind of virtual event, defined in a base class, which has no implementation of its own.  A derived class must define every abstract event (as well as every other abstract member) using the override keyword.

    public abstract class Dog 
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        public abstract event EventHandler<string> Barked;

        public virtual void Bark()
        {
            Console.WriteLine("Woof");
        }
    }

    public class Terrier : Dog
    {
        public Terrier(string name) : base(name) { }

        public override event EventHandler<string> Barked;

        public override void Bark()
        {
            Console.WriteLine("Yip yip");
            Barked(this, "Yip yip");
        }
    }

The class in which the abstract event is defined must be defined as an abstract class, using the abstract keyword.

A derived class may not hide the abstract event in the base class, using the new keyword.  It must implement the inherited abstract event and the new event must be virtual, defined using the override keyword.

Example of using this event:

            Terrier t = new Terrier("Jack");
            t.Barked += (o, s) => Console.WriteLine("- " + s + " -");
            t.Bark();

1163-001

#1,162 – Virtual Events Support Polymorphism

In C#, polymorphism is implemented using virtual members–which can be methods, properties, indexers or events.

A virtual event has an implementation in the base class that can be overridden in a derived class.  When the event’s add or remove accessors are invoked, the specific accessor used is determined at run-time based on the type of the underlying object.

A virtual event is defined in the base class using the virtual keyword.

        // Code from Dog class
        private EventHandler barked;
        public virtual event EventHandler Barked
        {
            add
            {
                barked += value;
                Console.WriteLine("Dog.Barked add accessor");
            }
            remove
            {
                barked -= value;
                Console.WriteLine("Dog.Barked remove accessor");
            }
        }

A virtual event is overridden in a derived class using the override keyword.

        // Code from Terrier : Dog class
        private EventHandler myBarked;
        public override event EventHandler Barked
        {
            add
            {
                myBarked += value;
                Console.WriteLine("Terrier.Barked add accessor");
            }
            remove
            {
                myBarked -= value;
                Console.WriteLine("Terrier.Barked remove accessor");
            }
        }

Using the event:

            Dog d = new Dog("Bob");
            d.Barked += (s, e) => { Console.WriteLine("Dog.Barked"); };

            Terrier t = new Terrier("Jack");
            t.Barked += (s, e) => { Console.WriteLine("Terrier.Barked"); };

            Dog d2 = t;
            d2.Barked += (s, e) => { Console.WriteLine("Terrier.Barked"); };

1162-001

#1,161 – Explicit Implementation of Events in Interface

If you implement an event defined in an interface explicitly, you must use event accessor syntax, defining both add and remove accessors.

    public interface IDogEvents
    {
        event EventHandler Barked;
        event EventHandler Escaped;
    }

    public class Dog : IDogEvents
    {
        public string Name { get; set; }
        public bool IsPresent { get; set; }

        public Dog(string name)
        {
            Name = name;
            IsPresent = true;
        }

        public void Bark()
        {
            Console.WriteLine("Woof");
            if (IsPresent)
                barked(this, EventArgs.Empty);
            else
                escaped(this, EventArgs.Empty);
        }

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

        private EventHandler escaped;
        event EventHandler IDogEvents.Escaped
        {
            add { escaped += value; }
            remove { escaped -= value; }
        }
    }

You can then add an event handler by using an interface reference.

        static void Main(string[] args)
        {
            Dog d = new Dog("Bob");
            IDogEvents ide = (IDogEvents)d;
            ide.Barked += d_Barked;
            d.Bark();
        }

#1,160 – Implementing Events Defined in an Interface

A class that implements an interface needs to define any events that are part of the interface.  Below is an example.

    public interface IDogEvents
    {
        event EventHandler Barked;
        event EventHandler Escaped;
    }

    public class Dog : IDogEvents
    {
        public string Name { get; set; }
        public bool IsPresent { get; set; }

        public Dog(string name)
        {
            Name = name;
            IsPresent = true;
        }

        public void Bark()
        {
            Console.WriteLine("Woof");
            if (IsPresent)
                Barked(this, EventArgs.Empty);
            else
                Escaped(this, EventArgs.Empty);
        }

        public event EventHandler Barked = delegate { };

        public event EventHandler Escaped = delegate { };
    }

#1,159 – Events vs. Delegates, part II

You can use a delegate in place of an event by simply removing the event keyword.  Using an event is preferred, however, because the event syntax prevents different consumers of the event from impacting each other.

Specifically, using a public field that is a delegate allows client code to do any of the following.  These actions are all prohibited when using an event.

  • Prevent other subscribers from being invoked by directly changing the delegate’s invocation list.
  • Delete invocation list by reassigning the delegate instance to null
  • Invoke the delegate directly

These are all actions that should only be allowed by the producer of the event and not by any of its consumers.  The event syntax prevents consumers from interfering with each other.

#1,158 – Events vs. Delegates, part I

You’ll typically define an event rather than using a public delegate.  Although either method will work, events provide some benefits over using delegates directly.

Below, we define a delegate type and a Barked event of that type in a Dog class:

    public delegate void DogDelegate(string dogName);

    public class Dog
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        public event DogDelegate Barked = delegate { };

        public void Bark()
        {
            Console.WriteLine("Woof");
            Barked(Name);
        }
    }

We can now handle the event as follows:

        static void Main(string[] args)
        {
            Dog d = new Dog("Bob");
            d.Barked += d_Barked;
            d.Bark();

            Console.ReadLine();
        }

        static void d_Barked(string dogName)
        {
            Console.WriteLine(dogName + " just barked");
        }

1158-001
This code would all work the same if we simply removed the event keyword, making the delegate available to the client code.  (See part II for more information).

#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));
            }
        }

#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));
        }