IEnumerable and IObservable, a story of duality

IEnumerable has been around in the Microsoft .NET framework since the first versions. Reactive Extensions(Rx) a new library for composing asynchronous and event-based programs using observable collections. Before we dive into this exiting new world it’s handy to explore the duality that exists between IEnumerable and IObservable. IObservable is one of the new interfaces available since the .NET 4.0 framework. If you’re not familiar with IObservable, I propose you first read my previous post.

[more]

Remember IEnumerable?

Basically our IEnumerable<T> object provides us with a enumerator represented by the IEnumerator<T> interface with support for a simple iteration over a collection of any generic type. This behaviour is possible due to the implementation of the IEnumerator<T> interface. Let’s start with a example that’s just a iteration over a collection of animals where some of them have legs.

IEnumerable<Animal> animals = // I'm sure you get this ;-) 

var results =   from animal in animals
                where animal.Legs > 0
                select animal;

foreach (var a in results)
{
    Console.WriteLine(a.Name);
}

The above Animal object must of course implement IEnumerable<T> (in this case IEnumerable<Animal>). This IEnumerable<T> returns our IEnumerator object. This can be seen in it’s definition:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}


As you can see, the IEnumerable<T> exposes an IEnumerator<T> object each time the GetEnumerator method is called. When you look at the IEnumerator’s definition you’ll notice that it only consists of 3 methods. The Reset method was put in place primarily for COM interopability. The Current property keeps returning the same object until MoveNext is called. This MoveNext method sets Current to the next element if there is a next element available. 

public interface IEnumerator<T>
{
    T Current { get; }
    bool MoveNext();
    void Reset();
}

Let’s for a moment apply what we have learned to the first example. Let’s assume we have a zoo with a list of all animals. Due to some undefined magic, all gates are left open, so we need a list of all animals capable of escaping (let’s for the purpose of this example assume that all animals without legs can’t escape). The where-clause in the query expression will filter all those animals that have legs. In the foreach, we’ll encounter a series of subsequent MoveNext and Current calls. This will continue until MoveNext returns false (when Current is the last element).

So what about duality?

As said before, IObservable is ‘dual’ with IEnumerable. What is this thing called duality? Duality is a mathematical concept that can be found in different fields like formal logic (De Morgan’s law) or calculus and analytics (limit and colimit of functions).

IEnumerable and IObservable both deal with collections, but the main difference between them is how they present it to the developer. While IEnumerable’s approach is to keep asking “give me the next object” (pull). IObservable on the other hand provides this objects to its subscribers (push). The main difference is that with IEnumerable the consumer is in control of getting the next element, while in IObservable it’s the producter who’s in control.

Let’s take the IEnumerable’s definition to explore the duality in depth:

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

In order to turn IEnumerable in a IObservable we first need dualize the methods. In this case the dual for GetEnumerator is Subscribe. Since GetEnumerator takes no arguments, Subscribe will return void.

Since GetEnumerator returns IEnumerator<T>, our Subscribe method will have the equivalent IObservable<T> as argument. Applying these changes get us to the IObservable interface:

public interface IObservable<out T> : IEnumerable
{
    void Subscribe(IObserver<T> observer);
}

Next let’s move on to our IEnumerator interface, where we’ll also begin with the basic definition. (Yes, we left our the reset method since it’s only there for legacy purposes).

public interface IEnumerator
{
    new T Current { get; }
    bool MoveNext();
}

So let’s start our transformation again. The current property (getter property) will be turned into a MoveNext property (and a setter):

public interface IEnumerator
{
    new T OnNext { set; }
    bool MoveNext();
}

But wait, since a property is basically just a method, and we’ll only use the setter-functionality of it; the final interface will contain a method OnNext.

public interface IEnumerator
{
    void OnNext(T value);
    bool MoveNext();
}

Our second method MoveNext, gives us a few additional questions. In the IEnumerator contract this method returns a bool (which makes absolutely sense), but that ain’t exactly true. Our MoveNext method can throw an exception (e.g. InvalidOperationException). In this case we would need two different return types (bool or Exception). Because of this, the dual form will actually be two methods: OnError (for the exceptions) and OnCompleted when MoveNext returns false. You may wondering about what’s happening when MoveNext() returns true. Actually, think about it carefully. Does such case concerns to subscribers? no it does not or we might say it is dual to OnNext()method. So finally will get following contracts:

public interface IObserver<in T>
{
     void OnCompleted();
     void OnError(Exception error);
     void OnNext(T value);
}

Now both of our interfaces are taking form, and when we put them together we got:

public interface IObservable<T> : IEnumerable
{
    void Subscribe(IObserver<T> observer);
}

public interface IObserver<T> : IDisposable, IEnumerator
{
     void OnCompleted();     void OnNext(T value);
     void OnError(Exception error);
}

When we look back we may notice how we forgot one interface in the road. On the way we have seen how IEnumerable.GetEnumerator turns into IObservable.Subscribe() step by step. However, what previously was IEnumerator and have became an IObserver both implements IDisposable interface as well. Actually we could say that IEnumerable.GetEnumerator() could be defined as:

IEnumerator<T>, IDisposable IEnumerator.GetEnumerator()

On the other hand, during such transformation we got a Subscribe() method but not UnSubscribe one. In the same way we want an observer to be subscribed to observable collection we might want to unsubscribe it. It could easily resolve by leaving IDisposable interface as returning type as follows:

public interface IObservable<T>
{
    IDisposable Subscribe(IObserver<T> observer);
}

public interface IObserver<T> : IDisposable, IEnumerator
{
     void OnCompleted();     void OnNext(T value);
     void OnError(Exception error);
}

 

 

 

 

Concluding

The really interesting part here is (beyond the theoretical explanation) is that any developer could understand that an event or any other push-based notification or async technology can be handled with LINQ in the same way that whatever IEnumerable oject does. Of course the whole IObserverable story will  become even more valuable when using it in combination with Reactive Extensions (Rx), but more on that subject later.

24. August 2010 by Jeroen Verhulst
Categories: Uncategorized | Leave a comment

Leave a Reply

Required fields are marked *

*