Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Argument type is not assignable to parameter type and it should be

Tags:

c#

generics

I have defined a class CommandProcessor<T> as having T derive from Command and contain a default constructor:

public class CommandProcessor<T> : ICommandProcessor<T> where T : Command, new()

The Command type itself also defines a default constructor and it implements an interface, ICommand. The interface contains a method that expects an input parameter to be of type T:

void Process(T command);

So I expect that I am able to define a class:

public class SpecificCommandProcessor : CommandProcessor<SpecificCommand>

it will work because SpecificCommand inherits from Command and also provides a default constructor.

All good so far.

But Visual Studio 2013 with C# 4.5 won't compile the following line:

CommandProcessor<Command> test = new SpecificCommandProcessor();

saying that it cannot convert the source type to the target type.

This means that I can also not do the following:

List<CommandProcessor<Command>> myList = new List<CommandProcessor<Command>>;
var toAdd = new SpecificCommandProcessor();
myList.Add(toAdd);

I have tried direct casting and safe casting, but none is accepted by the compiler. Yet it is obvious that SpecificCommandProcessor is in fact a CommandProcessor<Command>.

What am I missing here?

like image 392
Roy Dictus Avatar asked Dec 10 '22 19:12

Roy Dictus


2 Answers

SpecificCommandProcessor is NOT a CommandProcessor<Command>, it's a CommandProcessor<SpecificCommand> - and those things are different.

Take, for example, List<Animal> and List<Sheep> (with obvious inheritance).

List<Animal> animals = new List<Sheep>(); // if this were legal
animals.Add(new Wolf());                  // what should this do?

What you can do, is leverage generic interface covariance and contravariance in C#. For example, IEnumerable<T> is covariant, which means that this:

IEnumerable<Animal> animals = new List<Sheep>();

will actually work. That's because there is absolutely no way to add items to an IEnumerable after the fact, you can only get items from it, and the items you'll get will definitely be instances of Animal. It is actually defined using IEnumerable<out T>, where out means that the result will only be used as an output from the interface, so the value is ok if it's at least a T, or any inheritor.

What you might need to do is to create a covariant interface with

public interface ICommandProcessor<out T> where T : Command, new(){}

and have the CommandProcessor implement it:

public class CommandProcessor<T>:ICommandProcessor<T> where T : Command, new(){}

In that case, the code:

List<ICommandProcessor<Command>> myList = new List<ICommandProcessor<Command>>();
var toAdd = new SpecificCommandProcessor();
myList.Add(toAdd);

compiles and works (provided that the classes are indeed keeping the covariance promise)

like image 55
SWeko Avatar answered Mar 16 '23 00:03

SWeko


To understand the logic behind why covariance cannot work in this case, consider two "specific" command processors:

public class CreateCommandProcessor : CommandProcessor<CreateCommand>

public class DeleteCommandProcessor : CommandProcessor<DeleteCommand>

Then imagine we do this:

CommandProcessor<Command> processor = new CreateCommandProcessor();

Now, as far as the compiler is concerned, processor is an object that can process a command. Any command. So the following should be valid:

processor.Process(new DeleteCommand());

Except... it isn't valid. Because processor can actually only process Create commands. This is a contradiction. This is why the assignment is invalid.

More generally, this is why a generic interface taking T as a method parameter cannot be covariant.

It's not clear exactly how useful it is to have a list of objects that can all handle completely different inputs but if the idea is to create a command queue of sorts (or similar) consider creating something like a list of invocations instead. Something like:

// Note non-generic interface
public class CommandInvocation<T> : ICommandInvocation
{
    public CommandInvocation<T>(T command, CommandProcessor<T> processor)
    {
        // Assign params to fields...
    }

    public void Invoke()
    {
        _processor.Process(_command);
    }
}

Then you can do the following:

var invocations = new List<ICommandInvocation>();
invocations.Add(new CommandInvocation<CreateCommand>(createCommand,
                                                  new CreateCommandProcessor()));

invocations.Add(new CommandInvocation<DeleteCommand>(deleteCommand,
                                              new DeleteCommandProcessor()));

Depending on your use case, you could take this a step further and create a CommandInvocationFactory that injects some kind of processor resolver to give you the right processor for a given command type (so you don't have to pass the command processor explicitly each time), e.g.:

public ICommandInvocation Get<T>(Command<T> command)
{
    var processor = _processorFactory.Get<T>();
    return new CommandInvocation<T>(command, processor);
}

Then you can just do:

invocations.Add(_invokerFactory.Get(new CreateCommand()));
like image 27
Ant P Avatar answered Mar 16 '23 00:03

Ant P