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

Implementing Observer pattern in .NET

, 28 Apr 2007
Rate this:
Please Sign up or sign in to vote.
The observer pattern should be used whenever one or more objects (observers) are interested to know the states of subject

Introduction

The Observer pattern should be used whenever one or more objects (observers) are interested in knowing the states of subject. The subject maintains a list of its observers. Each observer must register as an observer with the subject.

In this article, I have described how to implement the Observer pattern using C# cool features such as events and delegates.

Observer Pattern

Design Patterns are meant to provide a common vocabulary for communicating design principles. The Observer Pattern is classified under Object Behavioral Patterns in the book, Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al. (Addison-Wesley, 1995). According to this book, definition of Observer pattern is:

"Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."

Scenario

Say, code up a FileArchive class which will notify interested parties whenever files are added or deleted. That means any class can publish a set of events, to which other objects can subscribe at run time.

In this article, consider a banking application where, I want various objects in the system to be able to obtain information on currency rate changes.

Solution

Anyone who has done GUI programming will be familiar with the idea of events, where an object tells interested parties that something significant has occurred. An example might include a button object telling its parent form that it has been pressed. Sometimes we need this type of event outside of GUI programming.

Events in C# are based on delegates, with the originator defining one or more callback functions. A callback function is a function which one piece of code defines and another implements; in other words, one piece of code says, "If you implement a function which looks like this, I can call it". A class that wants to use events defines callback functions as delegates, and the listening object then implements them. In this article, I have tried to discuss how to implement Observer pattern with the help of this event delegate.

Consider a class named CurrencyRateWatcher which gets currency rate from various sources (through Webservice). If it's found that currency rate is changed, then this object can notify other objects in the banking system when a currency rate changes. In this case, I'll use a class called CurrencyRateChangeObserver, so I can simply show how events are set up. I'll use the classes like this:

public static Main(String[] args)
{
            //Create Watcher object
            CurrencyRateWatcher
             oRateWatcher = new CurrencyRateWatcher ();
            //Create an Observer object
            CurrencyRateChangeObserver
            obs = new CurrencyRateChangeObserver(oRateWatcher);
            oRateWatcher.Notify("Dollar", 78,DateTime.Now);
}

The CurrencyRateWatcher is created and started to monitor the currency rates. Then create a CurrencyRateChangeObserver, passing it a reference to the watcher object. The CurrencyRateChangeObserver tells the CurrencyRateWatcher that it is interested in being notified, so its callback method will get called and adjust with the new value of currency whenever CurrencyRateWatcher detects that the currency rate has changed.

The delegate method that is used for the notification takes two arguments. The first is a reference to the object that has originated the notification, and the second contains any data which needs to be passed as part of the notification process. In our example, we'll want to pass over the name of the currency (such as Dollar, Pound, and Euro), new value and the change time. This information is passed in the second argument as a reference to a class derived from the system class EventArgs:

Class CurrencyRateChangeInfo : EventArgs
{
      public string CurrencyName;  // name of the currency
      public double newRate;       // new rate value
      public double changeTime;    // change time
      public CurrencyRateChangeInfo(string name, double rate, double chTime)
            { 
                      CurrencyName = name;
                      newRate  =  rate;
                      changeTime = chTime;
            }
}  

A CurrencyRateChangeInfo is an object that simply holds the name of a currency, the new value for the currency and the time of change.

Now I am going to implement the CurrencyRateWatcher class itself. The first task is to define the delegate that will be used for callbacks. As I mentioned above, event delegates take two arguments, one representing the sender object, and the second the data passed with the event:

Class CurrencyRateWatcher
{     
    public delegate void CurrencyRateChange (object sender,CurrencyRateChangeInfo info);
}

Now add a reference to an event object:

Class CurrencyRateWatcher
{
   public delegate void CurrencyRateChange(object sender,CurrencyRateChangeInfo info);
   public event CurrencyRateChange OnRateChange;
}  

The second line of the above code defines an event object of type CurrencyRateChange, called OnRateChange. I don't implement this object-but only declare a public reference to it. Clients will create the delegate object and attach it to my reference, so that when I use the event object, I am calling back to them.

Notification

The final part of CurrencyRateWatcher is the notification method, which calls back to the client:

Class CurrencyRateWatcher
{
    public delegate void CurrencyRateChange(object sender, CurrencyRateChangeInfo info);
    public event CurrencyRateChange OnRateChange;
    public void Notify(string name, double newValue)
    {   if(OnRateChange != null)
        {
        CurrencyRateChangeInfo cRateChange = new CurrencyRateChangeInfo(name,newValue);
        OnRateChange(this, cRateChange);
        }
    }
}

In the notification method, first I check the event reference whether it is null or not. If the event reference is null, then no one has registered with me. If it isn't null, I create a CurrencyRateChangeInfo object to hold the event data, and then use the delegate to call back to the client.

Client Who is Interested to be Notified

Now I am going to implement the client, in the form of the CurrencyRateChangeObserver class:

class CurrencyRateChangeObserver
{
    //Reference to the watcher we're working with
    CurrencyRateWatcher wr;
    public CurrencyRateChangeObserver(CurrencyRateWatcher  wr)
        {
            this.wr = wr;
        }
    // The callback method
    public void RateHasChanged (object sender, CurrencyRateChangeInfo oInfo)
        {      
        Console.WriteLine("Rate '{0}' has
        new value '{1}'
        and the change Time is{2}", oInfo.
        CurrencyName,
        oInfo.newRate, oInfo.changeTime);
    }  
} 

First, I implement the constructor to store away the reference we're passed. I also implement the delegate in the form of the RateHasChanged method, which takes two arguments that were defined in CurrencyRateWatcher. This method just gets the name of the currency, new value and change time- from the CurrencyRateChangeInfo object, and writes them to the command window.

Set Up Client

The final piece of the code is to link callback method to the event reference in the watcher object:

class CurrencyRateChangeObserver
{
    //Reference to the watcher we're working with
    CurrencyRateWatcher wr;
    public CurrencyRateChangeObserver(CurrencyRateWatcher  wr)
        {
            this.wr = wr;
        // set up the delegate
            wr.OnRateChange += 
		new CurrencyRateWatcher.CurrencyRateChange(RateHasChanged);
        }
    // The callback method
    public void RateHasChanged (object sender, CurrencyRateChangeInfo oInfo)
        {      
        Console.WriteLine("Rate '{0}' has
        new value '{1}'
        and the change Time is{2}", oInfo.
        CurrencyName,
        oInfo.newRate, oInfo.changeTime);
    }  
}

Using the standard delegate syntax, I create a CurrencyRateWatcher. CurrencyRateChange delegate is passed a reference to callback method, and saved to the public OnRateChange member of the watcher. Here '+=' symbol is being used in this assignment - this is a very useful property of delegates and allows us to chain delegate references together (+= is actually the operator overloading for registering with event). Thus, if more than one client registers itself with the same CurrencyRateWatcher, their delegates will get concatenated, and when OnRateChange is called in the watcher, all registered callback methods will get called one after another. And so we can see how it all fits together.

History

  • 29th April, 2007: Initial post

License

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

About the Author

S.M. Rabiul Islam
Web Developer
Bangladesh Bangladesh
This is S.M. Rabiul Islam from Bangladesh, I have been working as a software engineer here in Bangladesh at offshore software development house

Comments and Discussions

 
GeneralInspiration PinmemberLain Kou Mro15-Nov-07 2:25 
GeneralRe: Inspiration PinmemberMoim Hossain15-Nov-07 2:28 
GeneralJust a wrapper and bad naming [modified] PinmemberSimone Busoli1-May-07 6:43 
GeneralRe: Just a wrapper and bad naming PinmemberSebastien Lorion1-May-07 7:20 
GeneralRe: Just a wrapper and bad naming PinmemberSimone Busoli1-May-07 7:38 
AnswerRe: Just a wrapper and bad naming PinmemberSebastien Lorion1-May-07 8:46 
GeneralRe: Just a wrapper and bad naming PinmemberSimone Busoli1-May-07 9:41 
GeneralRe: Just a wrapper and bad naming PinmemberSebastien Lorion1-May-07 10:06 
GeneralRe: Just a wrapper and bad naming PinmemberSimone Busoli1-May-07 10:26 
AnswerRe: Just a wrapper and bad naming PinmemberSebastien Lorion1-May-07 10:53 
99.99% of events in .NET Framework are following this pattern. Consistency is one of the key qualities of any framework. I have nothing more to say.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com

GeneralRe: Just a wrapper and bad naming PinmemberSimone Busoli1-May-07 11:40 
GeneralRe: Just a wrapper and bad naming PinmemberKindo Malay25-May-08 14:23 
Generalthis is redundant PinmemberPalisade30-Apr-07 5:06 
GeneralRe: this is redundant Pinmemberaecdev30-Apr-07 5:21 
GeneralRe: this is redundant PinmemberHerre Kuijpers1-May-07 2:09 
GeneralRe: this is redundant PinmemberAdam Tibi1-May-07 5:19 
GeneralNice...and helpful Pinmemberzakirul29-Apr-07 22:43 
GeneralNice article Pinmembermarlongrech29-Apr-07 20:00 

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
Web01 | 2.8.140721.1 | Last Updated 29 Apr 2007
Article Copyright 2007 by S.M. Rabiul Islam
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid