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

本文关键字:类型 参数 分配 不可分 可分配 | 更新日期: 2023-09-27 18:22:19

我定义了一个类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>

我在这里错过了什么?

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

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 或任何继承器,则该值是可以的。

您可能需要做的是创建一个协变接口

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

并让命令处理器实现它:

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

编译和工作(前提是类确实保持协方差承诺(

要了解在这种情况下协方差无法工作背后的逻辑,请考虑两个"特定"命令处理器:

public class CreateCommandProcessor : CommandProcessor<CreateCommand>
public class DeleteCommandProcessor : CommandProcessor<DeleteCommand>

然后想象一下我们这样做:

CommandProcessor<Command> processor = new CreateCommandProcessor();
现在,就

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

processor.Process(new DeleteCommand());

除了。。。它无效。因为processor实际上只能处理创建命令。这是一个矛盾。这就是分配无效的原因。

更一般地说,这就是为什么将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()));

为了让编译器满意,你需要在 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不变的,这就是编译器告诉你的;如果它是不变的,那么你就不能"安全地"利用任何类型的方差。

当你开始遇到这样的死胡同时,也许你应该退后一步,考虑一下通用方法是否真的是最好的解决方案。可能非通用接口会使这变得容易得多。