参数类型不能分配给参数类型,它应该是

时间:2015-11-18 11:07:52

标签: c# generics

我已将某个类CommandProcessor<T>定义为T派生自Command并包含默认构造函数:

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

Command类型本身也定义了一个默认构造函数,它实现了一个接口ICommand。该接口包含一个方法,该方法要求输入参数的类型为T

void Process(T command);

所以我希望我能够定义一个类:

public class SpecificCommandProcessor : CommandProcessor<SpecificCommand>

它会起作用,因为SpecificCommand继承自Command并且还提供了默认构造函数。

到目前为止一切都很好。

但是带有C#4.5的Visual Studio 2013将无法编译以下行:

CommandProcessor<Command> test = new SpecificCommandProcessor();

说它无法将源类型转换为目标类型。

这意味着我也不能执行以下操作:

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

我尝试过直接投射和安全投射,但编译器都没有接受。但很明显,SpecificCommandProcessor实际上是CommandProcessor<Command>

我在这里缺少什么?

3 个答案:

答案 0 :(得分:5)

SpecificCommandProcessor不是CommandProcessor<Command>,而是CommandProcessor<SpecificCommand> - 这些都是不同的。

例如,List<Animal>List<Sheep>(具有明显的继承)。

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

您可以做的是利用C#中的通用接口协方差和逆变。例如,IEnumerable<T>是协变的,这意味着:

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

实际上会有效。这是因为事实上绝对没有办法向IEnumerable添加项目,你只能从中获取项目,而你获得的项目肯定是Animal的实例。 它实际上是使用IEnumerable<out T>定义的,其中out表示结果将仅用作接口的输出,因此如果它至少是T或任何值,则值为ok继承者。

您可能需要做的是使用

创建协变界面
public interface ICommandProcessor<out T> where T : Command, new(){}

让CommandProcessor实现它:

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

在这种情况下,代码:

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

编译和工作(假设这些类确实保持协方差承诺)

答案 1 :(得分:3)

要理解为什么协方差在这种情况下不起作用的逻辑,请考虑两个“特定”命令处理器:

public class CreateCommandProcessor : CommandProcessor<CreateCommand>

public class DeleteCommandProcessor : CommandProcessor<DeleteCommand>

然后想象我们这样做:

CommandProcessor<Command> processor = new CreateCommandProcessor();

现在,就编译器而言,processor是一个可以处理命令的对象。 任何命令。所以以下内容应该是有效的:

processor.Process(new DeleteCommand());

除了......它无效。因为processor实际上只能处理Create命令。这是一个矛盾。这就是作业无效的原因。

更一般地说,这就是将T作为方法参数的通用接口不能协变的原因。

目前还不清楚具有可以处理完全不同输入的对象列表是多么有用,但如果想要创建各种(或类似的)命令队列,请考虑创建类似于调用列表的内容。类似的东西:

// 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);
    }
}

然后您可以执行以下操作:

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

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

根据您的使用情况,您可以更进一步,创建一个CommandInvocationFactory注入某种处理器解析器,为您提供适合给定命令类型的处理器(所以您不必每次都明确地传递命令处理器,例如:

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

然后你可以这样做:

invocations.Add(_invokerFactory.Get(new CreateCommand()));

答案 2 :(得分:2)

为了让编译器满意,你需要在T:

中设置ICommandProcessor<T>协变
public interface ICommandProcessor<out T> where T : ICommand
{
}

现在以下编译正常:

ICommandProcessor<ICommand> test = new SpecificCommandProcessor(); //not the use of interfaces.

List<ICommandProcessor<ICommand>> myList = new List<ICommandProcessor<ICommand>>(); //again note the use of interfaces
var toAdd = new SpecificCommandProcessor();
myList.Add(toAdd);

但是我们还没有真正解决任何问题,因为当您添加以下内容(根据您的要求)时会出现问题:

public interface ICommandProcessor<out T> where T : ICommand //covariant in T
{
    void Foo(T t) //WILL NOT COMPILE. Contravariant in T
}

所以基本上T必须同时是协变和逆变的,这是不可能的;因此T是不变的,这就是编译器告诉你的;如果它是不变的,那么你就不能“安全地”利用任何类型的方差。

当你开始遇到像这样的deadends时,也许你应该退后一步,考虑一般方法是否真的是最好的解决方案。可能非通用的接口会使这更容易。