I am trying to figure out why using the is
keyword seems to differ when I cast. Here's a test case showing what I am looking at:
[TestFixture]
public class TestMy {
public interface IBaseMessage { }
public interface IMessageProcessor<T> where T : IBaseMessage {
void Process(T msg);
}
public class RRMessage : IBaseMessage {
}
public class BaseMessageProcessor {
public bool CanProcess<T>(T msg) where T : IBaseMessage {
return this is IMessageProcessor<T>;
}
}
public class RRMessageProcessor : BaseMessageProcessor, IMessageProcessor<RRMessage> {
public void Process(RRMessage msg) {
throw new NotImplementedException();
}
}
[Test]
public void Test1() {
var msgProcessor = new RRMessageProcessor();
var msg = new RRMessage();
Console.WriteLine(msgProcessor.CanProcess(msg));
}
[Test]
public void Test2() {
var msgProcessor = new RRMessageProcessor();
var msg = new RRMessage() as IBaseMessage;
Console.WriteLine(msgProcessor.CanProcess(msg));
}
Why does Test1() return True
and Test2() return False
. How can I implement CanProcess()
so that it returns True
in both cases?
The type inference in Test1
means it's calling
msgProcessor.CanProcess<RRMessage>()
... whereas in Test2
it's calling
msgProcessor.CanProcess<IBaseMessage>()
Now RRMessageProcessor
doesn't implement IMessageProcessor<IBaseMessage>
- you can't call Process(justAnyMessage)
- it only knows how to process RRMessage
values. So it's really entirely reasonable, basically.
To make both tests return true, you don't really want a generic method, because you only care about the execution-time type of msg
. There are a few options here:
Have a private generic method, but call it with an argument of type dynamic
, in which case the type inference will be performed using the execution-time type of the message:
public bool CanProcess(IBaseMessage message)
{
dynamic d = message;
// Perform type inference at execution time
return CanProcess(d);
}
private bool CanProcess<T>(T message)
{
return this is IMessageProcessor<T>;
}
Check the interfaces using reflection, e.g. calling typeof(IMessageProcessor<>).MakeGenericType(msg.GetType()).IsAssignableFrom(GetType())
Note that you still need to be slightly careful - suppose you had some SpecialRRMessage
deriving from RRMessage
... then an RRMessageProcessor
could still reasonably process it, but it doesn't implement IMessageProcessor<SpecialRRMessage>
.
You may want to change your IMessageProcessor<T>
interface so that T
is declared to be contravariant:
public interface IMessageProcessor<in T> where T : IBaseMessage
At that point, your RRMessageProcessor
would implement IMessageProcessor<RRMessage>
due to contravariance.
See more on this question at Stackoverflow