Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.InvalidCastException: Unable to cast object of type x to type y

I have a custom messaging system which I am trying to convert so it can support plugins.

The system has producers and consumers. Both must specify the type of message they produce or consume.

For consumers the Interface to be implemented is

IConsumer<AMessage>

I have a consumer in a plugin dll which implements this Interface:

FileProcessor : IConsumer<FileMessage>

With

FileMessage : AMessage

And AMessage is an abstract class. Both IConsumer and AMessage are in the main (Core) assembly.

Now, in the core, which loaded the plugin and scanned & found the consumers I want to link the consumers into the messaging system.

To simplify things here I just try to put the consumer in a variable:

IConsumer<AMessage> consumer = IConsumer<AMessage> new FileProcessor();

I get a warning in VS:

Suspicious cast:there is no type in the solution which is inherited from both 'TestPlugin.FileProcessor' and 'Core.IConsumer'.

And when I execute I get

System.InvalidCastException: Unable to cast object of type 'TestPlugin.FileProcessor' to type 'Core.IConsumer`1[Core.AMessage]'.

Why does this cast fail? How can I solve this?

--edit1--

based on comments, here is the full definition of IProducer and IConsumer:

public interface IProducer<out T> : IProcessor where T : AMessage
{
    event MessageSender<T> SendMessage;
}

public interface IConsumer<in T> : IProcessor where T : AMessage
{
    ProcessResult ProcessBean(T bean);
}

when Producers are linked in the system, they define T, after that only consumers with the same type of T can be linked to that producer. For example:

var channel = flow.From(FileProducer).To(FileConsumer);

With flow.From:

public Channel<T> From<T>(IProducer<T> producer) where T : AMessage
{
    ...
}

and channel.To:

public class Channel<T> where T : AMessage 
{

   public Channel<T> To(IConsumer<T> consumer){
      ...
   }
}

So the producer defines the type of T for the consumers.

--edit1--

--edit2--

The need for the down-cast is because I am trying to reconstruct the channels from an JSON definition in the Core system.

So Consumers (and Producers) are dynamically constructed from the plugins assemblies and added to a temporary dictionary (when detected and instantiated):

var consumers = new Dictionary<string, IProcessor>();

I cannot use IConsumer here for the consumers since T is not necessarily the same for all consumers.

Then when building the Channels, I'll lookup the consumer by it's id (key of dictionary) and provide it to the To(...) method of the channel. That fails because the dictionary holds IProcessor and the dynamically constructed channel has a "To" signature of public Channel<T> To(IConsumer<T> consumer).

--edit2--

--solution--

Thanks to Luaan (see comments) I reached to the following solution:

When scanning the plugin assemblies I originally stored the detected Producers and Consumers in a dictionary of <string, IProcessor>, now I changed this dictionary to <string, dynamic> which solved this casting issue!

Thanks to all who added to getting to this solution!

@Luaan, I wished I could select your answer as the solution, but it is in a comment...

--solution--

Thanks!

like image 659
mvermand Avatar asked Oct 30 '22 08:10

mvermand


1 Answers

You need to mark your interface as covariant with the out keyword:

IConsumer<out AMessage>

This means you can pass a more derived type than the one specified, so you can use a FileMessage which derives from AMessage.

Seeing your edits, you don't need covariance but contravariance (IConsumer<in AMessage>) but then this cast:

IConsumer<AMessage> consumer = (IConsumer<AMessage>)new FileProcessor();

isn't valid. From msdn:

Contravariance Enables you to use a more generic (less derived) type than originally specified. You can assign an instance of IEnumerable<Base> to a variable of type IEnumerable<Derived>.

like image 85
Alexander Derck Avatar answered Nov 09 '22 17:11

Alexander Derck