我已将某个类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>
。
我在这里缺少什么?
答案 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时,也许你应该退后一步,考虑一般方法是否真的是最好的解决方案。可能非通用的接口会使这更容易。