Ruben Bartelink
Ruben Bartelink

Reputation: 61815

Should IObservable be preferred over events when exposing notifications in a library targeting .NET 4+

I have a .NET library which, as part of an Object Model will emit notifications of certain occurrences.

It would seem to me that the main pros of events are approachability for beginners (and simplicity in certain consumption contexts) with the main negative being that they are not composable and hence are immediately forced into an Observable.FromEvent* if you want to do anything interesting without writing a code thicket.

The nature of the problem being solved is such that the event traffic won't be particularly frequent or voluminous (it's definitely not screaming RX), but there is definitely no requirement to support .NET versions prior to 4.0 [and hence I can use the built-in IObservable interface in System.Reactive without forcing any significant dependencies on consumers]. I'm interested in some general guidelines some specific concrete reasons to prefer IObservables over events from an API design perspective though - regardless of where my specific case might sit on the event - IObservable spectrum.

So, the question:

Is there anything concrete I'm making dramatically more difficult or problematic for API consumers if I go with the simplest thing and expose an event instead of an IObservable

Or, restated: Aside from the consumer having to do an Observable.FromEvent* to be able to compose events, is there really not a single reason to prefer an IObservable over an event when exposing a notification in an API?

Citations of projects that are using IObservable for not-screaming-RX stuff or coding guidelines would be ideal but are not critical.


NB as touched on in the comments with @Adam Houldsworth, I'm interested in concrete things wrt the API surface of a .NET 4+ library, not a survey of opinions as to which represents a better 'default architecture' for our times :)

NB this question has been touched on in IObserver and IObservable in C# for Observer vs Delegates, Events and IObservable vs Plain Events or Why Should I use IObservable?. The aspect of the question I'm asking has not been addressed in any of the responses due to SRP violations. Another slightly overlapping question is Advantages of .NET Rx over classic events?. Use of IObservable instead of events falls into that same category.

Upvotes: 30

Views: 4058

Answers (4)

James World
James World

Reputation: 29786

In the comments of this answer, OP refined his question as:

[Is it] indeed definitely the case that each and every event can always be Adapted to be an IObservable?

To that question, the answer is basically yes - but with some caveats. Also note that the reverse is not true - see the section on reverse transformation and the conclusion for reasons why observables might be preferred to classic events because of the additional meaning they can convey.

For strict translation, all we need to do is map the event - which should include the sender as well as arguments - on to an OnNext invocation. The Observable.FromEventPattern helper method does a good job of this, with the overloads returning IObservable<EventPattern<T>> providing both the sender object and the EventArgs.

Caveats

Recall the Rx grammar. This can be stated in EBNF form as: Observable Stream = { OnNext }, [ OnError | OnCompleted ] - or 0 or more OnNext events optionally followed by either an OnCompleted or an OnError.

Implicit in this is the idea that from the view of an individual subscriber events do not overlap. To be clear, this means that a subscriber will not be called concurrently. Additionally, it is quite possible that other subscribers can be called not only concurrently but also at different times. Often it is subscribers themselves that control pace of event flow (create back-pressure) by handling events slower than the pace at which they arrive. In this situation typical Rx operators queue against individual subscribers rather than holding up the whole subscriber pool. In contrast, classic .NET event sources will more typically broadcast to subscribers in lock-step, waiting for an event to be fully processed by all subscribers before proceeding. This is the long-standing assumed behaviour for classic events, but it is not actually anywhere decreed.

The C# 5.0 Language Specification (this is a word document, see section 1.6.7.4) and the .NET Framework Design Guidelines : Event Design have surprisingly little to say on the event behaviour. The spec observes that:

The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events.

The C# Programming Guide : Events section says that:

When an event has multiple subscribers, the event handlers are invoked synchronously when an event is raised. To invoke events asynchronously, see Calling Synchronous Methods Asynchronously.

So classic events are traditionally issued serially by invoking a delegate chain on a single thread, but there is no such restriction in the guidelines - and occasionally we see parallel invocation of delegates - but even here two instances of an event will usually be raised serially even if each one is broadcast in parallel.

There is nothing anywhere I can find in the official specifications that explicitly states that event instances themselves must be raised or received serially. To put it another way, there is nothing that says multiple instances of an event can't be raised concurrently.

This is in contrast to observables where it is explicitly stated in the Rx Design Guidelines that we should:

4.2. Assume observer instances are called in a serialized fashion

Note how this statement only addresses the viewpoint of an individual subscriber instance and says nothing about events being sent concurrently across instances (which in fact is very common in Rx).

So two takeways:

  • Whilst OnNext captures the idea of an event it is possible that the classic .NET event may violate the Rx Grammar by invoking events concurrently.
  • It is common for pure Rx Observables to have different semantics around the delivery of events under load because back-pressure is typically handled per subscriber rather than per-event.

As long as you don't raise events concurrently in your API, and you don't care about the back-pressure semantics, then translation to an observable interface via a mechanism like Rx's Observable.FromEvent is going to be just fine.

Reverse Transformation

On the reverse transformation, note that OnError and OnCompleted have no analogue in classic .NET events, so it is not possible to make the reverse mapping without some additional machinery and agreed usage.

For example, one could translate OnError and OnCompleted to additional events - but this is definitely stepping outside of classic event territory. Also, some very awkward synchronization mechanism would be required across the different handlers; in Rx, it is quite possible for one subscriber to receive an OnCompleted whilst another is still receiving OnNext events - it's much harder to pull this off in a classic .NET events transformation.

Errors

Considering behaviour in error cases: It's important to distinguish an error in the event source from one in the handlers/subscribers. OnError is there to deal with the former, in the latter case both classic events and Rx simply (and correctly) blow up. This aspect of errors does translate well in either direction.

Conclusion

.NET classic events and Observables are not isomorphic. You can translate from events to observables reasonably easily as long as you stick to normal usage patterns. It might be the case that your API requires the additional semantics of observables not so easily modelled with .NET events and therefore it makes more sense to go Observable only - but this is a specific consideration rather than a general one, and more of a design issue than a technical one.

As general guidance, I suggest a preference for classic events if possible as these are broadly understood and well supported and can be transformed - but don't hesitate to use observables if you need the extra semantics of source error or completion represented in the elegant form of OnError and OnCompleted events.

Upvotes: 15

Adam Houldsworth
Adam Houldsworth

Reputation: 64487

To address your headline question:

No they should not be preferred on the basis that they exist in .NET 4 and are available to use. Preference depends on intended use, so a blanket preference is unwarranted.

That said, I would tend towards them as an alternative model to traditional C# events.

As I have commented on throughout this question, there are many ancillary benefits to approaching the API with IObservable, not least of which is external support and the range of choice available to the end consumer.

To address your inner question:

I believe there would be little difficulty between exposing events or IObserable in your API as there is a route to one from the other in both cases. This would put a layer over your API, but in actuality this is a layer you could also release.

It is my opinion that choosing one over the other isn't going to be part of the deciding reason why someone choose to use or not use your API.

To address your re-stated question:

The reason might be found in why there is an Observable.FromEvent in the first place :-) IObservable is gaining support in many places in .NET for reactive programming and forms part of many popular libraries (Rx, Ix, ReactiveUI), and also interoperates well with LINQ and IEnumerable and further into the likes of TPL and TPL DataFlow.

A non-Rx example of the observable pattern, so not specifically IObservable would be ObservableCollection for XAML apps.

Upvotes: 1

samy
samy

Reputation: 14962

I've been reading a lot about Reactive extensions before finally dipping my toe, and after some rough starts I found them really interesting and useful.

Observables extension have this lovely optional parameter where you can pass your own time manager, effectively letting you manipulate time. In my case it helped a lot since I was doing some time related work (check this webservice every ten minutes, send one email per minute, etc) and it made testing a breeze. I would plug the TestScheduler in the component under test and simulate one day of events in a snap.

So if you have some workflows in your library where time plays a role in orchestration, I would really recommend using Observables as your output.

However if you are just raising events in direct responses to user inputs, I don't think that it's worth the added complexity for your users. As you noted they can wrap the event into their own Observable if needed.

You could have your cake and eat it too, although it would mean more work; offer a facade that turns your event library into a Observable-fueled one, by creating said Observable from your events. Or do it the other way: have an optional facade that suscribes to your observable and raise a classic event when triggered.

In my opinion there is a non-trivial technical step to take when dealing with reactive extensions and in this case it may come down to what your API consumers would be the most comfortable using

Upvotes: 3

Pedro.The.Kid
Pedro.The.Kid

Reputation: 2078

The IObservable is the IEnumerable of events, so the only question here is do you think IObservable will become the standard as the IEnumerable is now so yes its preferable, if you think its just a passing by thing that will fall in future use event instead.

The IObservable is better than event in most cases but I personally think I'll forget to use it as its not very common to be used and when the time arrives I'll have forgotten about it.

I know my answer is not of great help but only time will tell if RX will become the standard, I think it has good chances.

[EDIT] To make it more Concrete.

To the end user the only difference is that one is an interface an the other is not, making the interface more testable and expandable, because different sources can implement the same interface.

As Adam Houldsworth said one can be changed to the other easily to make no other difference.

Upvotes: 1

Related Questions