.NET 4 Baby Steps: Part IV - Observer
_
_Note: 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 that pushes data to subscribers, and .NET 4 introduces two new interfaces, IObservable<T> and IObserver<T>, which make implementing this pattern very easy.
To see the usage of these interfaces, I’d like to use an example of a device with a GPS sensor that checks its position every second. If the GPS position leaves a specified area, we want to notify the cops.
GPSPosition
First, we need a small class to represent our location data—a simple data class with these key points:
- Implements
IComparableto check when a boundary is exceeded. - Overrides
ToStringfor a clean output. - Has two constructors: a default one and one that takes latitude and longitude (using
@longsincelongis a reserved keyword in C#).
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;
}
// Longitude is the same
return 0;
}
}
GPSSensor
Next, we create a fake sensor that reports our position. Key points:
- Implements
IObservable<T>to provideGPSPositiondata. - Maintains a list of observers (
List<T>). - Uses
observer.OnNextto send updates andobserver.OnCompletedwhen tracking ends (e.g., boundary exceeded). GetPosition()updates observers if the position changes.
class GPSSensor : IObservable<GPSPosition>
{
private readonly List<IObserver<GPSPosition>> observers = new List<IObserver<GPSPosition>>();
private readonly Random random = new Random();
public GPSPosition Position { get; set; }
private GPSPosition boundary;
public GPSSensor()
{
this.Position = new GPSPosition();
this.Position.Lat = random.Next(0, 181);
this.Position.Long = random.Next(0, 361); // Fixed: longitude range (0-360)
boundary = 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 = (Position.Lat + random.Next(0, 5)) % 181; // Fixed: wrap around
Position.Long = (Position.Long + random.Next(0, 5)) % 361; // Fixed: wrap around
foreach (IObserver<GPSPosition> observer in observers)
{
observer.OnNext(this.Position);
if (this.Position.CompareTo(boundary) > 0) // Fixed: compare current position
{
observer.OnCompleted();
}
}
}
public IDisposable Subscribe(IObserver<GPSPosition> observer)
{
observers.Add(observer);
observer.OnNext(this.Position);
return observer as IDisposable;
}
}
Map
The Map class is a subscriber that handles output and alerts when the boundary is crossed. Key points:
- Implements
IObserver<GPSPosition>to receive updates. - Covers
OnCompleted,OnError, andOnNext.
class Map : IObserver<GPSPosition>
{
private GPSPosition lastKnown;
public bool StillTracking { get; private set; }
public void OnCompleted()
{
Console.WriteLine("The device has moved beyond the bounds of our checking. Notify the cops: 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
Finally, we tie everything together in Main():
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 output like the example below: