Hager Aly
Hager Aly

Reputation: 1143

what are the differences between .net observer pattern variations (IObservable and event delegation) and when to use each?

i just want to know when to use each and what the advantages of each i really struggle to understand why .net introduced IObservable/IObserver after having the Event Delegation and according too MSDN Event Delegation is Preferred

Based on our understanding of the Observer pattern, let us now turn our attention to the use of this pattern within the .NET Framework. Those of you with passing familiarity of the types exposed in the FCL will note that no IObserver, IObservable, or ObservableImpl types*are present in the Framework. The primary reason for their absence is the fact that the CLR makes them obsolete after a fashion. Although you can certainly use these constructs in a .NET application, the introduction of *delegates and events provides a new and powerful means of implementing the Observer pattern without developing specific types dedicated to support this pattern. In fact, as delegates and events are first class members of the CLR, the foundation of this pattern is incorporated into the very core of the .NET Framework. As such, the FCL makes extensive use of the Observer pattern throughout its structure.

so why they added IObservable to .net 4.0

Upvotes: 1

Views: 1149

Answers (1)

Enigmativity
Enigmativity

Reputation: 117084

I found the reference to the quoted text on the MSDN site. I have to say that I'm stunned. It just seems to be an ill-informed and incorrect view of the implementation of IObservable<T>.

Those of you with passing familiarity of the types exposed in the FCL will note that no IObserver, IObservable, or ObservableImpl types are present in the Framework.

This is correct. The IObservable<T> and IObserver<T> interfaces we derived as a mathematical dual of the IEnumerable<T> and IEnumerator<T>. It flipped collections from being something you requested values synchronously to something that asynchronously had values pushed to you. Here's what Matthew Podwysocki said about the duality:

We remember from our first post in the series where we talked about the pull (interactive) versus the push (reactive) model. The pull model, represented by the iterator pattern of IEnumerable<T>/IEnumerator<T> states that we must explicitly call a method in order to get each item from our abstracted collection. On the other hand, our push model, represented by the observable pattern of IObservable<T>/IObserver<T> states that we register an interest through a subscription and then items are subsequently handed to us from some abstracted collection.

Back to your quoted text.

The primary reason for their absence is the fact that the CLR makes them obsolete after a fashion. Although you can certainly use these constructs in a .NET application, the introduction of delegates and events provides a new and powerful means of implementing the Observer pattern without developing specific types dedicated to support this pattern.

This seems completely backwards to me. Delegates and events have been in the framework since v1.0. If they made IObservable<T>/IObserver<T> obsolete then there would have been no need to introduce them (which is the crux of your question).

My memory of the time was that Microsoft was so convinced of the value of this pair of interfaces that they rushed to include them in the BCL so that developers could write their own basic code prior to the full System.Reactive implementation was released.

In fact, as delegates and events are first class members of the CLR, the foundation of this pattern is incorporated into the very core of the .NET Framework. As such, the FCL makes extensive use of the Observer pattern throughout its structure.

This again is an interest view of what "first class members" means. Wikipedia says:

In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.

Events are certainly not first-class citizens. You cannot pass around an event and you cannot raise an event independantly to the class that declared it.

Take this simple example:

void Main()
{
    var foo = new Foo();
    EventHandler bar = foo.Bar;
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}

I get the following error when I try to compile:

CS0070 The event 'Foo.Bar' can only appear on the left hand side of += or -= (except when used from within the type 'Foo')

I can only subscribe to an event if I have a reference to instance of the class that defines the event and I can only raise the event from within the same class.

Not so with Observables.

This code compiles nicely:

void Main()
{
    var foo = new Foo();
    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);
}

public void SimpleExample(IObservable<EventPattern<EventArgs>> example)
{
    example.Subscribe(x => { });
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}

Observables are first-class citizens of C# (and VB.NET and F#), but events are not.

And while the standard event model is a form of the observer pattern it isn't always easy to use.

Try this code:

void Main()
{
    var foo = new Foo();

    foo.Bar += (s, e) => Console.WriteLine("Bar!");
    foo.Bar -= (s, e) => Console.WriteLine("Bar!");

    foo.OnBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}

When run it still produces "Bar!" on the console.

To correctly unsubscribe you must retain a reference to the original handler. This works:

void Main()
{
    var foo = new Foo();

    EventHandler handler = (s, e) => Console.WriteLine("Bar!");
    foo.Bar += handler;
    foo.Bar -= handler;

    foo.OnBar();
}

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}

Observables handles this much more cleanly:

void Main()
{
    var foo = new Foo();

    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);

    IDisposable subscription = SimpleAttach(bar);
    SimpleDetach(subscription);

    foo.OnBar();        
}

public IDisposable SimpleAttach(IObservable<EventPattern<EventArgs>> example)
{
    return example.Subscribe(x => Console.WriteLine("Bar!"));
}

public void SimpleDetach(IDisposable subscription)
{
    subscription.Dispose();
}   

public class Foo
{
    public event EventHandler Bar;
    public void OnBar()
    {
        this.Bar?.Invoke(this, new EventArgs());
    }
}

Not only can the event (observable) be passed around, but also the ability to detach the handler can be passed around without reference to the original handler itself. In my example Console.WriteLine("Bar!") isn't even in the same method that unsubscribes.

This leads to the ability to do things like having a single List<IDisposable> disposables to store all subscriptions in a single place that can be used to cleanly detach from all events. Just do disposables.ForEach(x => x.Dispose);.

And Observables are awesome for combining multiple paradigms in a single query. Like this:

void Main()
{
    var foo = new Foo();

    IObservable<EventPattern<EventArgs>> bar =
        Observable
            .FromEventPattern<EventHandler, EventArgs>(
                h => foo.Bar += h,
                h => foo.Bar -= h);

    var query =
        from ep in bar
        from name in Observable.FromAsync(() => GetNameAsync()) // async Task<string> GetNameAsync()
        from data in Observable.Start(() => LoadData(name)) // string LoadData(string name)
        select new { name, data };

    var subscription =
        query
            .Subscribe(x => Console.WriteLine($"{x.name} = {x.data}"));
}

The query is beautiful and succinct.

I rarely use the standard event model. I almost always use Observables.

Observables were added because they were and still are a far more powerful abstraction of the observer pattern than the builtin eventing model.

Upvotes: 7

Related Questions