C
C#7mo ago
Rettoph

Contravariant Help

Given the following code:
c#
MySubscriber subscriber = new MySubscriber();

class MessageA { }
class MessageB : MessageA { }
class MessageC : MessageB { }

// note the in keyword here
interface ISubscriber<in T>
{
void Process(T message);
}

class MySubscriber : ISubscriber<MessageA>, ISubscriber<MessageB>
{
void ISubscriber<MessageA>.Process(MessageA message)
{
Console.WriteLine("Processing MessageA");
}

void ISubscriber<MessageB>.Process(MessageB message)
{
Console.WriteLine("Processing MessageB");
}
}
c#
MySubscriber subscriber = new MySubscriber();

class MessageA { }
class MessageB : MessageA { }
class MessageC : MessageB { }

// note the in keyword here
interface ISubscriber<in T>
{
void Process(T message);
}

class MySubscriber : ISubscriber<MessageA>, ISubscriber<MessageB>
{
void ISubscriber<MessageA>.Process(MessageA message)
{
Console.WriteLine("Processing MessageA");
}

void ISubscriber<MessageB>.Process(MessageB message)
{
Console.WriteLine("Processing MessageB");
}
}
How might I take an instance of MySubscriber and create a ISubscriber<MessageC>[] with 2 values. The first being the ISubscriber<MessageA> implementation, and the second being the ISubscriber<MessageB> implementation?
5 Replies
Rettoph
Rettoph7mo ago
The end goal being
c#
MySubscriber subscriber = new MySubscriber();
ISubscriber<MessageC>[] c_subscribers = subscriber.GetAllApplicableSubscribers<MessageC>();
MessageC message = new MessageC();
foreach(ISubscriber<MessageC> c_subscriber in c_subscribers)
{
c_subscriber.Process(message);
}
c#
MySubscriber subscriber = new MySubscriber();
ISubscriber<MessageC>[] c_subscribers = subscriber.GetAllApplicableSubscribers<MessageC>();
MessageC message = new MessageC();
foreach(ISubscriber<MessageC> c_subscriber in c_subscribers)
{
c_subscriber.Process(message);
}
with an output of
Processing MessageA
Processing MessageB
Processing MessageA
Processing MessageB
phaseshift
phaseshift7mo ago
use System.Type::GetInterfaces to get all the interfaces, then check each one against your target interface type with Type::IsAssignableFrom (or ...To) Thats how you could collect different ones together in a general sense, but since you have explicit impls I presume that wont work, thinking about it. You'd have to wrap in a lambda/utility adapter that does more casting
Rettoph
Rettoph7mo ago
After asking around on the MonoGame server one thing ive found is that my question probably wasnt very clear My question is about contravariance at its core. Given the code above, the following is valid:
c#
ISubscriber<MessageC> subscriber = new MySubscriber();
subscriber.Process(new MessageC()); // Currently outputs: "Processing MessageA"
c#
ISubscriber<MessageC> subscriber = new MySubscriber();
subscriber.Process(new MessageC()); // Currently outputs: "Processing MessageA"
As MySubscriber implements both ISubscriber<MessageA> and ISubscriber<MessageB>, and the ISubscriber interfaces utilizes the in keyword i can cast MySubscriber directly to ISubscriber<MessageC>. The issue is i want to be able to invoke all ISubscriber implementations that can be casted to ISubscriber<MessageC>. Doing ((ISubscriber<MessageC>)new MySubscriber()).Process() is valid, but only gives me the first one. Its an issue because ISubscriber<MessageC> is ambiguous, there are two interfaces that can meet that criteria. Even with reflection i cant seem to find a way to invoke the ISubscriber<MessageB> implementation
c#
MySubscriber subscriber = new MySubscriber();

Action<MessageC> process = typeof(MySubscriber)
.GetInterfaceMap(typeof(ISubscriber<MessageB>))
.InterfaceMethods[0]
.CreateDelegate<Action<MessageC>>(subscriber);

process(new MessageC()); // Output: "Processing MessageA"
c#
MySubscriber subscriber = new MySubscriber();

Action<MessageC> process = typeof(MySubscriber)
.GetInterfaceMap(typeof(ISubscriber<MessageB>))
.InterfaceMethods[0]
.CreateDelegate<Action<MessageC>>(subscriber);

process(new MessageC()); // Output: "Processing MessageA"
phaseshift
phaseshift7mo ago
What happens if you use MessageB in CreateDele?
Rettoph
Rettoph7mo ago
Sorry for the lack of response, The same result happens regardless of the type parameter used. I found a work around for my personal use case so marking this as closed.