我已经将类定义CommandProcessor<T>为T派生自Command并包含默认构造函数:
public class CommandProcessor<T> : ICommandProcessor<T> where T : Command, new()
Run Code Online (Sandbox Code Playgroud)
该Command类型本身还定义了一个默认的构造函数,它实现了一个接口,ICommand.该接口包含一个期望输入参数类型为的方法T:
void Process(T command);
Run Code Online (Sandbox Code Playgroud)
所以我希望我能够定义一个类:
public class SpecificCommandProcessor : CommandProcessor<SpecificCommand>
Run Code Online (Sandbox Code Playgroud)
它将起作用,因为SpecificCommand继承自Command并且还提供默认构造函数.
到目前为止都很好.
但是带有C#4.5的Visual Studio 2013将无法编译以下行:
CommandProcessor<Command> test = new SpecificCommandProcessor();
Run Code Online (Sandbox Code Playgroud)
说它无法将源类型转换为目标类型.
这意味着我也不能做以下事情:
List<CommandProcessor<Command>> myList = new List<CommandProcessor<Command>>;
var toAdd = new SpecificCommandProcessor();
myList.Add(toAdd);
Run Code Online (Sandbox Code Playgroud)
我尝试过直接投射和安全投射,但编译器都没有接受.然而很明显,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?
Run Code Online (Sandbox Code Playgroud)
您可以做的是利用C#中的通用接口协方差和逆变.例如,IEnumerable<T>是协变,这意味着:
IEnumerable<Animal> animals = new List<Sheep>();
Run Code Online (Sandbox Code Playgroud)
实际上会有效.那是因为在事实之后绝对没有办法将项目添加到IEnumerable,你只能从中获取项目,你将获得的项目肯定是实例Animal.它实际上是使用定义的IEnumerable<out T>,其中out意味着结果将仅用作接口的输出,因此如果它至少是T或任何继承者,则该值是正确的.
您可能需要做的是创建一个协变接口
public interface ICommandProcessor<out T> where T : Command, new(){}
Run Code Online (Sandbox Code Playgroud)
让CommandProcessor实现它:
public class CommandProcessor<T>:ICommandProcessor<T> where T : Command, new(){}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,代码:
List<ICommandProcessor<Command>> myList = new List<ICommandProcessor<Command>>();
var toAdd = new SpecificCommandProcessor();
myList.Add(toAdd);
Run Code Online (Sandbox Code Playgroud)
编译和工作(假设课程确实保持协方差承诺)
要理解为什么协方差在这种情况下不起作用的逻辑,请考虑两个“特定”命令处理器:
public class CreateCommandProcessor : CommandProcessor<CreateCommand>
public class DeleteCommandProcessor : CommandProcessor<DeleteCommand>
Run Code Online (Sandbox Code Playgroud)
然后想象我们这样做:
CommandProcessor<Command> processor = new CreateCommandProcessor();
Run Code Online (Sandbox Code Playgroud)
现在,就编译器而言,processor它是一个可以处理命令的对象。任何命令。所以以下内容应该是有效的:
processor.Process(new DeleteCommand());
Run Code Online (Sandbox Code Playgroud)
除了...它是无效的。因为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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后您可以执行以下操作:
var invocations = new List<ICommandInvocation>();
invocations.Add(new CommandInvocation<CreateCommand>(createCommand,
new CreateCommandProcessor()));
invocations.Add(new CommandInvocation<DeleteCommand>(deleteCommand,
new DeleteCommandProcessor()));
Run Code Online (Sandbox Code Playgroud)
根据您的用例,您可以更进一步并创建一个CommandInvocationFactory注入某种处理器解析器的进程,以便为给定的命令类型提供正确的处理器(因此您不必每次都显式传递命令处理器) ,例如:
public ICommandInvocation Get<T>(Command<T> command)
{
var processor = _processorFactory.Get<T>();
return new CommandInvocation<T>(command, processor);
}
Run Code Online (Sandbox Code Playgroud)
然后你可以这样做:
invocations.Add(_invokerFactory.Get(new CreateCommand()));
Run Code Online (Sandbox Code Playgroud)
为了让编译器满意,您需要ICommandProcessor<T>在 T 中进行协变:
public interface ICommandProcessor<out T> where T : ICommand
{
}
Run Code Online (Sandbox Code Playgroud)
现在以下内容可以正常编译:
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);
Run Code Online (Sandbox Code Playgroud)
但我们还没有真正解决任何问题,因为当您添加以下内容(根据您的要求)时就会出现问题:
public interface ICommandProcessor<out T> where T : ICommand //covariant in T
{
void Foo(T t) //WILL NOT COMPILE. Contravariant in T
}
Run Code Online (Sandbox Code Playgroud)
所以基本上T必须同时是协变和逆变的,这是不可能的;因此T是不变的,这就是编译器告诉你的;如果它不变,那么您就无法“安全”地利用任何类型差异。
当您开始遇到这样的死胡同时,也许您应该退后一步,考虑通用方法是否真的是最佳解决方案。也许非通用接口会让这变得更加容易。