Click here to Skip to main content
Click here to Skip to main content

Implementing the Observer Pattern in C#

By , 11 Sep 2012
Rate this:
Please Sign up or sign in to vote.

Introduction

The Observer Pattern is arguably the one of the most interesting, if not the most important, design pattern in the GOF Design Pattern book1.

The intent of the pattern is to define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically2.

The objective of this article is to map this definition to the C#.net world, i.e. provide an implementation that is as simple as possible and that takes advantage of the framework.

I've read articles that touched on the subject. But I feel that they either fall short of mapping everything (see participants below); they try to re-implement without using available C# constructs, and/or fail to cite references.

This is also a note-to-self. Hopefully it will refine my understanding as I write it; and expand it from your comments.

1[Gamma1995]
2[Gamma1995], p.326

Background

from the GOF Design Pattern book3

Subject
  • knows its observers. Any number of Observer objects may observe a subject.
  • provides an interface for attaching and detaching Observer objects.
Observer
  • defines an updating interface for objects that should be notified of changes in a subject.
ConcreteSubject
  • stores state of interest to ConcreteObserver objects.
  • sends a notification to its observers when its state changes.
ConcreteObserver
  • maintains a reference to a ConcreteSubject object.
  • stores state that should stay consistent with the subject's.
  • implements the Observer updating interface to keep its state consistent with the subject's.

I've quoted this from the book because I felt it concisely defines what the pattern is and what the participants are.

Note that the book also tackles how to implement the pattern4 and gives sample codes in c++5 . I suggest reading the book because the ideas are quite useful and timeless.

3[Gamma1995],Structure and Participants, p.328
4[Gamma1995], p.330
5[Gamma1995], p.334

Delegates and Events

The easiest way to implement the observer pattern in .Net is using delegates and events.

Microsoft calls the class that raises events a publisher and the classes that handle events subscribers6. Notably, it is also known as "Publish-Subscribe" in the GOF book7.

I will try to define the components in the sequence how I normally write them in code. I find this useful when I'm reusing the pattern. Pardon my extensive use of bullets but I find them useful when making a point.

6[Events]
7[Gamma1995], p.326

Delegate

  • A delegate is a type that defines a method signature.8 I think it is a powerful construct that allows us to define an "interface", i.e. point of interaction9, without tight coupling.
  • All .Net Framework class library events are based on the System EventHandler delegate or the generic EventHandler<TEventArgs>.10 The following code shows the declaration of the System EventHandler.
    namespace System
    {
        public delegate void EventHandler(object sender, EventArgs e);
    }
    
  • You can define delegates any way you want but I suggest using System EventHandler or the generic version. Moreover, follow the event publication guideline.11
  • A delegate maps to the Observer in the pattern. It defines the interface, i.e. method signature, by which the observers will be notified. In .Net you don't even need to define your own Observer. You just need to use EventHandleror the generic variety.
8[Delegates]
9[Interface]
10[Guidelines]
11[Guidelines]

Event

  • The event is a keyword that is used as a modifier for delegates to declare an "event" in a class.12 An "event" is something of interest, e.g. a state changed or a method was invoked.
  • An event is a multicast delegate. It allows multiple methods with matching signatures; this doesn't need to be exact13, to subscribe.
  • The following code shows how an event based on the System EventHandleris declared.
    public class AClass
    {
        public event EventHandler MyEvent;
    }
    
  • The compiler automatically adds event accessors, add and remove.14 This makes the code above equivalent, sans locking, to the following code:
    private EventHandler myEvent;
    
    public event EventHandler MyEvent
    {
        add
        {
            click = (EventHandler)Delegate.Combine(click, value);
        }
    
        remove
        {
            click = (EventHandler)Delegate.Remove(click, value);
         }
    }
    
  • This shows how Event Handlers, i.e. Observers, are Attached and Detached.
  • To notify the Observers you just call the event. But the following is a more robust implementation that follows MS' guideline:
    protected virtual void OnAPropertyChanged(EventArgs e)
    {
        // Make a temporary copy of the event to avoid possibility of
        // a race condition if the last subscriber unsubscribes
        // immediately after the null check and before the event is raised.
        EventHandler handler = MyEvent;
    
        // Event will be null if there are no subscribers
        if (handler != null)
        {
            // Use the () operator to raise the event.
            handler(this, e);
        }
    }
    
  • Note that the event is initially null so you need to check if there are subscribers before invoking. And we invoke the copy to make sure it doesn't become null before we use it, i.e. to make it thread safe15.
  • An event maps to the Subject. It knows its Observers, i.e. subscribers. It allows any number of Observers, i.e. it's a multicast delegate. It provides an interface for attaching and detaching, i.e. add and remove.
12[event]
13[Variance]
14[Custom]
15[Thread]

ConcreteSubject

  • The ConcreteSubject stores the state of interest and sends notifications when the state changes. This map nicely to any class that has a state, i.e. a field, and declares an event for that field, or its accessor, the property.
  • The only caveat is GOF's diagram16 has the ConcreteSubject inheriting from the Subject. We can either wrap the event in an inheritable type, e.g. an interface or just let it be.
  • A sample of wrapping an event is the System ComponentModel IComponent:
    namespace System.ComponentModel
    {
        public interface IComponent : IDisposable
        {
        ...
            // Summary:
            //     Represents the method that handles the
            //     System.ComponentModel.IComponent.Disposed
            //     event of a component.
            event EventHandler Disposed;
        }
    }
    
  • One thing I find odd about MS' implementation is the state, i.e. IsDisposed, is defined in System.Windows.Forms.Control. I guess it's also odd that the event is under IComponent and not IDisposable. I agree that mixing in composition is better than just inheritance17 but wouldn't it be more readable and reusable to have the event, property et al. related to IDisposable be under it? 
  • I digress. The point is you can wrap your event in an interface or a base class so you can do type inheritance. But I think this is unnecessary. My take is composition, while not strictly type inheritance, is just as good if not better. As I mentioned earlier, delegates act like interfaces, similar to prototype-based inheritance models18. So just by declaring events you are already inheriting all of the goodness of delegates. In our sample before AClass is our ConcreteSubject. The only thing missing is the state of interest which we will implement using a field and a property. We will also hook-up the property setter to the event so we will have automatic notifications. Be sure to check the setter value if it is actually changing the state. This will save you a lot of unnecessary notifications.
    public class AClass
    {
        private int stateOfInterest;
    
        public event EventHandler StateOfInterestChanged;
    
        public int StateOfInterest
        {
            get 
            { 
                return this.stateOfInterest; 
            }
    
            set 
            {
                // Check if the new value is different
                // from the current to prevent unnecessary
                // triggers
                if (this.stateOfInterest != value)
                {
                    this.stateOfInterest = value;
                    this.OnStateOfInterestChanged(EventArgs.Empty);
                }
            }
        }
    
        protected virtual void OnStateOfInterestChanged(EventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler handler = this.StateOfInterestChanged;
    
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }
    }
    
16[OMT]
17[Composition]
18[Differential]

ConcreteObserver

  • That leaves us with the ConcreteObserver. In the .Net world it is simply the class that subscribes to the event. There are many ways to subscribe to events19 but try to use the programmatic approach so you have more control.
    public class BClass
    {
        AClass aclass;
    
        public BClass()
        {
            this.aclass = new AClass();
            this.aclass.StateOfInterestChanged +=
                this.AClass_StateOfInterestChanged;
        }
    
        void AClass_StateOfInterestChanged(object sender, EventArgs e)
        {
            throw new NotImplementedException();
            // or do something meaningful
        }
    }
    
  • Remember that when properly implemented you can cast the sender to an AClass. So even if you have more than one AClass, e.g. a collection of AClass, you can use the same handler.
    public class CClass
    {
        private ObservableCollection<aclass> listOfAClass;
    
        public CClass()
        {
            this.listOfAClass = new ObservableCollection<aclass>();
            this.listOfAClass.CollectionChanged += 
                new NotifyCollectionChangedEventHandler(
                    this.ListOfAClass_CollectionChanged);
        }
    
        private void ListOfAClass_CollectionChanged(
            object sender, 
            NotifyCollectionChangedEventArgs e)
        {
            // we will just use a blanket approach to 
            // hooking up added or removed objects
            e.OldItems.Cast<aclass>()
                .ToList<aclass>()
                .ForEach(
                a => a.StateOfInterestChanged -= 
                    this.AClass_StateOfInterestChanged);
    
            e.NewItems.Cast<aclass>()
                .ToList<aclass>()
                .ForEach(
                a => a.StateOfInterestChanged += 
                    this.AClass_StateOfInterestChanged); 
        }
    
        private void AClass_StateOfInterestChanged(
            object sender, EventArgs e)
        {
            throw new NotImplementedException();
    
            // or do something meaningful
        }
    }
    
19[Subscribe]

Summary

My object was to map the observer pattern to the .Net world using simple implementations. We achieved this by simply using delegates and events.

We added a few tweaks here and there, e.g. check if the property value is actually changing before triggering the event, or have an OnXXXChanged() method as described in the MS guideline. But overall implementing it is straight forward.

A bigger question I guess "how do I apply it?". Controls are great samples of the pattern; whether win form or web. For simple cases you can depict controls as the subject and the form or container as the observer, e.g. CheckBox has a CheckState and a CheckedStateChanged event, so one can easily see how this maps to the ConcreteSubject, State of Interest, and Subject.

But controls are more powerful when implemented as Observers using complex binding; and that, I think, is a subject for a different article.

History

  • 20120911 - Original Version

References

[Gamma1995]; Gamma, E., R. Helm, R. Johnson, and J. Vlissides; 1995; Design Patterns: Elements of Reusable Object-Oriented Software; Addison Wesley Professional.

[Delegates]; Delegates (C# Programming Guide); http://msdn.microsoft.com/en-us/library/ms173171(v=vs.100).aspx

[Events]; Events (C# Programming Guide); http://msdn.microsoft.com/en-us/library/awbftdfh(v=vs.100).aspx 

[Guidelines]; How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide); http://msdn.microsoft.com/en-us/library/w369ty8x(v=vs.100).aspx 

[event]; event (C# Reference); http://msdn.microsoft.com/en-us/library/8627sbea(v=vs.100) 

[Custom]; How to: Implement Custom Event Accessors (C# Programming Guide); http://msdn.microsoft.com/en-us/library/bb882534(v=vs.100).aspx

[Interface]; Interface (computing); http://en.wikipedia.org/wiki/Interface_(computing)

[Variance]; Using Variance in Delegates (C# and Visual Basic) http://msdn.microsoft.com/en-us/library/ms173174(v=vs.100).aspx 

[Thread]; Thread safety; http://en.wikipedia.org/wiki/Thread_safety 

[OMT]; Object-modeling technique; http://en.wikipedia.org/wiki/Object-modeling_technique

[Composition]; Composition over inheritance; http://en.wikipedia.org/wiki/Composition_over_inheritance 

[Differential]; Differential inheritance; http://en.wikipedia.org/wiki/Differential_inheritance 

[Subscribe] How to: Subscribe to and Unsubscribe from Events (C# Programming Guide); http://msdn.microsoft.com/en-us/library/ms366768(v=vs.100).aspx 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

acarpio1975
Software Developer
Australia Australia
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberDrABELL6-Oct-12 16:50 
GeneralRe: My vote of 5 Pinmemberacarpio19759-Oct-12 12:29 
GeneralRe: My vote of 5 PinmemberDrABELL9-Oct-12 12:43 
QuestionNot bad PinprotectorPete O'Hanlon11-Sep-12 23:34 
AnswerRe: Not bad Pinmemberacarpio197512-Sep-12 0:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 12 Sep 2012
Article Copyright 2012 by acarpio1975
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid