2

Suppose I have the following interface:

public interface IMessageProcessor<T> where T: BaseMessage {
        void Process(T msg);
    }

I have an abstract class that implements this interface:

 public abstract class AMessageProcessor<T> : IMessageProcessor<T> where T : BaseMessage {

        protected Controller Controller { get; private set; }

        public AMessageProcessor(Controller controller) {
            Controller = controller;
        }

        public abstract void Process(T msg);
    }

then I have a message:

 public class RRMessage : BaseMessage {
  ...
 }

and then I have an implementation:

public class RRMessageProcessor : AMessageProcessor<RRMessage> {
      public RRMessageProcessor(Controller controller) : base(controller) {}

      public override void Process(RRMessage msg) {

            //do something here
        }
    }

Now in another class I would like to make a list of these processors for different messages:

 public readonly List<AMessageProcessor<BaseMessage>> AvailableMessageProcessors;

        public MessageProcessingStrategy(Controller controller) {
            AvailableMessageProcessors = new List<AMessageProcessor<BaseMessage>>();

            /* ----- ERROR HAPPENS AT THIS LINE ------ */
            AvailableMessageProcessors.Add(new RRMessageProcessor(controller));

        }

And I get this error:

Error CS1503 Argument 1: cannot convert from 'RRMessageProcessor' to 'AMessageProcessor<BaseMessage>'

Seems like that conversion should work... Why can't it convert? How can I get it to work?

Denis
  • 11,796
  • 16
  • 88
  • 150
  • There's no safe way to do this. If it were allowed you could pass a `BaseMessage` to `RRMessage.Process` which requires an `RRMessage` parameter. – Lee Mar 03 '16 at 16:45

4 Answers4

1

I had a problem in my other answer (deleted) about covariant types in parameters but an approach like this may solves your problem:

  1. Defines a BaseMessageProcessor class (could be AMessageProcessor) like this one:

    public abstract class BaseMessageProcessor
    {
        protected Controller Controller { get; private set; }
    
        public BaseMessageProcessor(Controller controller)
        {
            Controller = controller;
        }
    
        public void Process<T>(T msg) where T : BaseMessage
        {
            if (this is IMessageProcessor<T>)
                (this as IMessageProcessor<T>).Process(msg);
    
            throw new NotSupportedException();
        }
    }
    
  2. Defines an interface IMessageProcessorOf<T>:

    public interface IMessageProcessor<T> where T : BaseMessage
    {
        void Process(T msg);
    }
    
  3. Defines concrete processors inheriting of BaseMessageProcessor and implementing (explicitly) IMessageProcessorOf<T>:

    public class RRMessageProcessor : BaseMessageProcessor, IMessageProcessorOf<RRMessage>
    {
        public RRMessageProcessor(Controller controller) : base(controller) { }
    
        void IMessageProcessor<RRMessage>.Process(RRMessage msg)
        {
            ...
        }
    }
    

This solution allows you to work with AvailableMessageProcessors:

public List<BaseMessageProcessor> AvailableMessageProcessors;

...

AvailableMessageProcessors = new List<BaseMessageProcessor>();
AvailableMessageProcessors.Add(new RRMessageProcessor(controller));

So, if you have 2 messages types like RRMessage and SSMessage, you can define one MultiMessageProcessor:

public class MultiMessageProcessor : BaseMessageProcessor, IMessageProcessorOf<RRMessage>, IMessageProcessorOf<SSMessage>
{
    public MultiMessageProcessor(Controller controller) : base(controller) { }

    void IMessageProcessorOf<RRMessage>.Process(RRMessage msg)
    {
        ...
    }

    void IMessageProcessorOf<SSMessage>.Process(SSMessage msg)
    {
        ...
    }
}

The calls to Process() method will be made through BaseMessageProcessor.Process<>:

multiProcessor.Process(new RRMessage());
multiProcessor.Process(new SSMessage());

Or just use RRMessageProcessor and define a SSMessageProcessor using the same idea like before.

Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53
  • awesome! This works great! and I like the flexibility of being able to process multiple messages in the same MessageProcessor! – Denis Mar 03 '16 at 18:04
0

This isn't the cleanest/prettiest way to do it but it seems to work. Here's what I changed in AMessageProcessor:

public abstract class AMessageProcessor : IMessageProcessor<BaseMessage>

        protected Controller Controller { get; private set; }

        public AMessageProcessor(Controller controller) {
            Controller = controller;
        }

        /* ----- REALLY DON'T WANT TO OVERRIDE THIS METHOD EVERYWHERE --- */
        public abstract void Process(BaseMessage msg);
    }

And then changing the RRMessageProcessor as:

public class RRMessageProcessor : AMessageProcessor, IMessageProcessor<RRMessage> {
      public RRMessageProcessor(Controller controller) : base(controller) {}

      /* ----- REALLY DON'T WANT TO OVERRIDE THIS METHOD LIKE THIS EVERYWHERE --- */
      public override void Process(BaseMessage msg) {
            Process(msg as RRMessage);
        }

      public void Process(RRMessage msg) {

            //do something here
        }
    }
Denis
  • 11,796
  • 16
  • 88
  • 150
  • You can avoid this by creating a wrapper class `ProcWrapper : IMessageProcessor` which contains an `IMessageProcessor` and does the cast in its `Process` method. This is the best you can do since this approach is unsafe anyway. – Lee Mar 03 '16 at 16:50
0

On your generic type you'll need to define the type as covariant. See https://msdn.microsoft.com/en-us/library/dd997386.aspx

This should work

public interface IMessageProcessor<in T> where T : BaseMessage
{
    void Process(T msg);
}
-1

You can easily solve your problem by redefining

public readonly List<AMessageProcessor<BaseMessage>> AvailableMessageProcessors;

into

public readonly List<RRMessageProcessor> AvailableMessageProcessors;

So you don't have cast problems and you're using the you're custom objects

hope to help!

user2543740
  • 172
  • 1
  • 10
  • But my list will contain different message processors, not just RRMessageProcessor - that is just one of the processors. My list will have other message processors that will inherit from AMessageProcessor – Denis Mar 03 '16 at 16:51
  • have a look at this: http://stackoverflow.com/questions/17506587/classes-hierarchy-and-casting-between-objects – user2543740 Mar 03 '16 at 16:57