I have two different event types implementing the same interface:
interface InputEvent { }
struct KeyboardEvent : InputEvent { }
struct MouseEvent : InputEvent { }
I have two streams, one of each event type:
IObservable<KeyboardEvent> KeyboardStream;
IObservable<MouseEvent> MouseStream;
I would like to introduce a merged IObservable<InputEvent> stream of both. At first I hoped that the compiler would automatically detect the base class:
IObservable<InputEvent> Merged = Observable.Merge(KeyboardStream, MouseStream);
No luck there, so I tried being explicit:
IObservable<InputEvent> Merged = Observable.Merge<InputEvent>(KeyboardStream, MouseStream);
Nope, compiler still doesn’t get the hint. So I cast each explicitly:
IObservable<InputEvent> Merged = Observable.Merge<InputEvent>((IObservable<InputEvent>)KeyboardStream, (IObservable<InputEvent>)MouseStream);
Yuck. And it still fails at runtime with a cast fail. I guess this is something to do with covariance (I still don’t fully get that on the first try…) so I’ll do what I would do with IEnumerable, use .Cast<T>():
IObservable<InputEvent> Merged = Observable.Merge<InputEvent>(KeyboardStream.Cast<InputEvent>(), MouseStream.Cast<InputEvent>());
Now the compiler tells me that .Cast<T>() is only defined for IObservable<Object>… What? That seems like a pretty inconvenient and unnecessary restraint.
Finally I try a simple select:
IObservable<InputEvent> Merged = Observable.Merge(KeyboardStream.Select(i => (InputEvent)i), MouseStream.Select(i => (InputEvent)i));
Success at last! It works and I can create my own simple extension method from it. However, the built-in .Cast<T>() and .OfType<T>() operators leave a pretty bad taste in my mouth. So my question is: why can’t I use the built-in .Cast<T>() extension on any observable other than Object which is pretty much redundant? Is it a covariance issue? An oversight in the Rx spec? A deliberate design decision?
The issue here was that my event types were
structs and therefore weren’t automatically boxed to Object. Changing the types toclassdid the trick.