Reputation: 902
I have a situation where I want to merge two observable sequences. The sequence consists of Messages like in Ex1 in the code example. I don't understand why it doesn't compile, when Ex2 compiles and runs as expected.
Can anyone explain this to me?
class Message<T> where T : IFoo {}
interface IFoo {}
class Foo : IFoo {}
class Bar : IFoo {}
class Program
{
static void Ex1()
{
var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Message<Foo>());
var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Message<Bar>());
o1.Merge<Message<IFoo>>(o2).Subscribe(Console.WriteLine);
Console.ReadKey();
}
static void Ex2()
{
var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Foo());
var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => new Bar());
o1.Merge<IFoo>(o2).Subscribe(Console.WriteLine);
Console.ReadKey();
}
}
Error in Visual Studio 2017 with .Net Framework 4.6.1:
Error CS1929 'IObservable<Message<Foo>>' does not contain a definition for 'Merge' and the best extension method overload 'Observable.Merge<Message<IFoo>>(IObservable<IObservable<Message<IFoo>>>, int)' requires a receiver of type 'IObservable<IObservable<Message<IFoo>>>'
Many thanks in advance!
Upvotes: 3
Views: 117
Reputation: 3697
In addition to other answers here:
interface IMessage<out T> where T : IFoo { }
class Message<T> : IMessage<T> where T:IFoo { }
interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }
class Program
{
static void Ex1()
{
var o1 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => (IMessage<IFoo>)new Message<Foo>());
var o2 = Observable.Interval(TimeSpan.FromMilliseconds(200)).Select(x => (IMessage<IFoo>)new Message<Bar>());
o1.Merge(o2).Subscribe(Console.WriteLine);
Console.ReadKey();
}
static void Main(string[] args)
{
Ex1();
}
}
Upvotes: 1
Reputation: 117029
Just because Foo
is derived from IFoo
it does not mean that Message<Foo>
derives from Message<IFoo>
- it does not - so you can not cast it like you have. The same applies to Bar
and IBar
.
Let's see why.
Instead of thinking in terms of Message<T>
, let's use List<T>
. I now have this code:
var foos = new List<Foo>();
foos.Add(new Foo());
foos.Add(new Foo());
That's good.
Now, like your question, when you try to change a Message<Foo>
into a Message<IFoo>
, let's try changing this to a List<IFoo>
:
List<IFoo> ifoos = (List<IFoo>)foos; // Does not compile
Let's say that it did compile - for the sake of explaining why your code didn't work - so I would then have a ifoos
that I could do this with:
ifoos.Add(new Bar());
After all, the reference I have for ifoos
is of type List<IFoo>
and Bar
derives from IFoo
so that would be perfectly valid code.
But ifoos
is just a cast of List<Foo>
and casting an object doesn't change its underlying type. A List<Foo>
can only accept elements that are of type Foo
(or derived from Foo
). Now, given our magic change to the compiler, we've created a situation that it can accept Bar
.
Boom! Run-time error.
That should clearly be why the cast (List<IFoo>)foos
should fail. And that's why the cast (Message<IFoo>)Message<Foo>
also fails in your question.
Upvotes: 1
Reputation: 263
We'll start with why the second method works.
The second method creates two collections of type Foo
and Bar
. But in the Merge
method the type to use is explicitly the interface IFoo
. This tells the method that this is the type of objects to store in the new collection: IEnumerable<IFoo>
. The collection accepts items of type IFoo
, and since Foo
and Bar
both inherit from the interface they're accepted by the collection. If you were to remove the type parameter from the Merge
method, or specify explicitly Foo
or Bar
as the type you would have the same error as the first method.
So what about the first method?
The first collection is of type Message<Foo>
. The generic type for this Message
is Foo
. Note that this is not the same as Message<IFoo>
, because the type is explicitly Foo
, not its superclasses or interfaces. Similarly Message<Bar>
has a generic type of Bar
, not IFoo
.
When the compiler comes to the Merge
method with two collections (one of Message<Foo>
and Message<Bar>
) it knows that the generic types of the two Messages
are not the same (a Foo
is an IFoo
and an Bar
is an IFoo
, but a Foo
is not a Bar
, and vice versa). So the compiler gives an error saying that it expects a collection of a related type (requires a receiver of type 'IObservable<IObservable<Message<IFoo>>>'
).
There's one final thing to be aware of.
The definition of the Message
class is class Message<T> where T : IFoo {}
. All the where
clause means means is that T
must be an IFoo
and that's it. It does not mean that a Message<Foo>
is a Message<IFoo>
. You can cast from a Foo
to an IFoo
and that's what happens in the second method, but they are not the same.
Upvotes: 1