I want to build a generic system of producers and consumers for a simple project.
What I have now is
public interface IMessage { }
public interface Message1 : IMessage { }
public interface Message2 : IMessage { }
public interface IConsumer<T> { }
public interface IProducer<T> { }
public class Mop1 : IConsumer<Message1>, IProducer<Message2>
{
}
class Program
{
static void Main(string[] args)
{
var list = new List<IConsumer<IMessage>>();
var mop = new Mop1();
list.Add(mop); // Error occurs here
}
}
The last line gives an error like cannot convert from 'Mop1' to 'IConsumer<GenericPubSub.IMessage>'
But Mop1 implements an IConsumer of an IMessage derived type. Is this some variance problem here? What is wrong about that?
This is tricky case. You can use co/contravariance, but...
I will simplify code a little. Just to shut up the compiler you can do that:
public interface IMessage { }
public interface Message1 : IMessage { }
public class Mop1 : IConsumer<Message1>
{
}
public interface IConsumer<out IMessage>
{
}
class Program
{
static void Main(string[] args)
{
var list = new List<IConsumer<IMessage>>();
var mop = new Mop1();
list.Add(mop); // Error occurs here
}
}
out IMessage
will do the trick as suggested in another answer, but it fundamentally doesn't fix anything. Let me show, now you want to make your interface:
public interface IConsumer<out IMessage>
{
object Process (IMessage m);
}
aaaand it will not compile. Because simply put if you say out IMessage
it means that return types should be derived from IMessage
, not parameter types.
So you will have it this way:
public interface IConsumer<in IMessage>
{
object Process (IMessage m);
}
public class Mop1 : IConsumer<Message1>
{
public object Process (Message1 msg)
{
return null;
}
}
To make it compile and be valid. But now your list.Add(mop);
will not work. And rightfully so, because your Mop1
is indeed not castable to IConsumer<IMessage>
because if it was following code would be possible:
list[0].Process (new Message2 ());
and it is not possible because Mop1 accepts only Message1
.
So to answer the question. You can't really do anything meaningful with message dispatching and pure C# compiler features. I see where it was going, you wanted static typing with messages and stuff. Unfortunately you can't have a list of consumers that consume specific messages in generic list, you have to have abstract signatures like bool CanProcess(IMessage); IMessage Process(IMessage);
and cast inside. You can also do it slightly better with custom attributes and reflection, but again, not purely with C# compiler.
In case you're planning to implement a typical producer, consumer pattern with these interfaces, please see this answer first.
If you want an IConsumer<TMessage>
to also be an IConsumer<TMessageBase>
, where TMessageBase
is any type that TMessage
inherits or implements, then you need to make your generic parameter covariant. Use the out
modifier.
public interface IConsumer<out T> { }
Now IConsumer<Message1>
is assignable to IConsumer<IMessage>
, since Message1
implements IMessage
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With