Skip to main content

13042010196Note: This post is part of a series and you can find the rest of the parts in the series index.

The pub/sub pattern is a simple concept, you have a provider which has data which it pushes to subscribers and .NET 4 brings in two new interfaces IObservable<T> and IObserver<T> which make implementing this pattern very easy.

To see the usage of these interfaces, I would like to use the example of a device which has a GPS sensor which every second checks it’s position and if the position of the GPS leaves a specified area we then want to notify the cops.

GPSPosition

First we need a small class which tells us where we are, i.e. it is just a data class. Some key points of this class are

  • I have implemented IComparable so I can check when the boundary is exceeded.
  • I’ve overridden ToString so it prints nicely.
  • I have two constructors, a default and one which takes a latitude and longitude so that I can copy the values from one object to another.
  • Note that my parameter is named @long – because long is a reserved keyword in C# you can append @ to use it.
class GPSPosition : IComparable<GPSPosition>
{
    public int Lat { get; set; }
    public int Long { get; set; }

    public GPSPosition() { }

    public GPSPosition(int lat, int @long)
    {
        this.Lat = lat;
        this.Long = @long;
    }

    public override string ToString()
    {
        return string.Format("Latitude = {0:N4}, Longitude = {1:N4}", Lat, Long);
    }
    
    public int CompareTo(GPSPosition other)
    {
        if (this.Lat > other.Lat)
        {
            return 1;
        }

        if (this.Lat < other.Lat)
        {
            return -1;
        }

        // latitude is the same

        if (this.Long > other.Long)
        {
            return 1;
        }

        if (this.Long < other.Long)
        {
            return -1;
        }

        // long is the same
        return 0;
    }     
}

GPSSensor

Now we need to create our fake sensor, which when asked tells us where in the world we are. Key points here are:

  • It implements IObservable<T> so it is a provider of GPSPosition data. That means we needed to implement the subscribe method so other objects can tell this class to send them the data.
  • We keep a list of the observers in a List<T>
  • We call the observer.OnNext to send data to it.
  • We call observer.OnCompleted when we are done with monitoring, which in our example is when we exceed the boundary.
  • Note that in the GetPosition method we are responsible for sending data to all the observers.
class GPSSensor : IObservable<GPSPosition>
{
    List<IObserver<GPSPosition>> observers = new List<IObserver<GPSPosition>>();
    Random random = new Random();
    public GPSPosition Position { get; set; }

    private GPSPosition boundry;

    public GPSSensor()
    {
        this.Position = new GPSPosition();
        this.Position.Lat = random.Next(0, 181);
        this.Position.Lat = random.Next(0, 181);

        boundry = new GPSPosition(this.Position.Lat + 10, this.Position.Long + 10);
    }

    public void GetPosition()
    {
        GPSPosition current = new GPSPosition(this.Position.Lat, this.Position.Long);

        Position.Lat += random.Next(0, 5);
        Position.Long += random.Next(0, 5);

        if (current.CompareTo(this.Position) != 0)
        {
            foreach (IObserver<GPSPosition> observer in observers)
            {
                observer.OnNext(this.Position);
                if (current.CompareTo(boundry) > 0)
                {
                    observer.OnCompleted();
                }
            }
        }
    }


    public IDisposable Subscribe(IObserver<GPSPosition> observer)
    {
        observers.Add(observer);
        observer.OnNext(this.Position);
        return observer as IDisposable;
    }
}

Map

Our third class, Map is a subscriber of data and it handles the outputting to the screen and notification of the cops when we move past the boundary. Key notes here:

  • It implements IObserver<GPSPosition> so it is a subscriber of GPS position data.
  • We implement the three methods from the interface:
    • OnCompleted for when we are done.
    • OnError in case something goes wrong.
    • OnNext for when new data is available.
class Map : IObserver<GPSPosition>
{
    private GPSPosition lastKnown;

    public bool StillTracking { get; private set; }

    public void OnCompleted()
    {
        Console.WriteLine("The device has moved beyond the boundsof our checking, notify the cops it was last seen at: {0}", lastKnown);
        StillTracking = false;
    }

    public void OnError(Exception error)
    {
        Console.WriteLine("SkyNet has taken over and shut down the GPS");
    }

    public void OnNext(GPSPosition value)
    {
        lastKnown = value;
        Console.WriteLine("At {0} we have moved to {1}", DateTime.Now, value);
        StillTracking = true;
    }
}

Main

We have created our data structure, our provider and and our subscriber - now we just need to bring them together in our main method, which is very easy:

public static void Main()
{
    GPSSensor sensor = new GPSSensor();
    Map map = new Map();
    sensor.Subscribe(map);
    do
    {
        sensor.GetPosition();
        Thread.Sleep(1000);
    } while (map.StillTracking);
}

This produces something that looks like:

image