我想使用泛型编写 CommandProcessor 。想法是通过单个对象( CommandProcessor 本身)发出 Command ,然后识别处理给定命令的命令处理程序。
但是,以下代码无法编译,我无法理解原因:
class GenericCommandProcessor : ICommandProcessor
{
private readonly IDictionary<Type, IList<ICommandHandler<ICommand>>> _handlers =
new Dictionary<Type, IList<ICommandHandler<ICommand>>>();
public void Register<TCommand>(ICommandHandler<TCommand> handler)
where TCommand : ICommand
{
IList<ICommandHandler<ICommand>> handlers = GetHandlers<TCommand>();
handlers.Add(handler); // <-- This doesn't compile
}
public void Process<TCommand>(TCommand command)
where TCommand : ICommand
{
IList<ICommandHandler<ICommand>> handlers = GetHandlers<TCommand>();
foreach (var commandHandler in handlers)
{
commandHandler.Handle(command);
}
}
private IList<ICommandHandler<ICommand>> GetHandlers<TCommand>()
{
Type commandType = typeof(TCommand);
IList<ICommandHandler<ICommand>> handlers;
if (!_handlers.TryGetValue(commandType, out handlers))
{
handlers = new List<ICommandHandler<ICommand>>();
_handlers.Add(commandType, handlers);
}
return handlers;
}
}
这是不编译的行:
handlers.Add(handler);
编译器返回以下错误:
cannot convert from 'GenericCommandHandlerTest.ICommandHandler<TCommand>' to 'GenericCommandHandlerTest.ICommandHandler<GenericCommandHandlerTest.ICommand>'
我希望如此,因为Register()
有一个通用约束:
where TCommand : ICommand
我通过解决IoC(在我的案例中使用Castle Windsor)中的命令处理程序列表来避免这个问题,而不是使用已注册处理程序列表的字典,但我很想理解为什么在CLR级别,此代码无法编译。我想我只是看不到树上的木头......
非常感谢提前。
答案 0 :(得分:4)
只需将您的方法更改为:
public void AddListItem(IListItem listItem)
{
_items.Add(listItem);
}
这里不需要使用泛型。
正如其他人已经说过的那样:即使没有更改,您的代码也会编译,所以请更新您的示例代码。
修复示例后, 更新:
您无法将ICommandHandler<TCommand>
类型的变量添加到IList<ICommandHandler<ICommand>>
,因为ICommandHandler<ICommand>
和ICommandHandler<TCommand>
是两种不同的类型,尽管TCommand
实现ICommand
}。如果它将工作,我的第一个答案将再次正确,你不需要首先使你的方法通用。
我猜Covariance会对此有所帮助,但不幸的是,在这种情况下不支持。
答案 1 :(得分:3)
修改的
正如丹尼尔所说,你所寻找的是协方差。
如果您使用的是C#4,这实际上是存在的,但不是一种有用的形式。要使此示例起作用,ICommandHandler
需要是逆变的。如果TCommand只是out
中的ICommandHandler
参数,您可以将ICommandHandler
定义为
interface ICommandHandler<out TCommand> where TCommand: ICommand { ... }
然后您就可以在ICommandHandler<TCommand>
中存储List<ICommandHandler<ICommand>>
,因为ICommandHandler<TCommand>
可以安全地转换为ICommandHandler<ICommand>
- 我们知道如果某些内容返回TCommand
,它会返回ICommand
。
但是,在您的情况下,TCommand
是in
参数,要将ICommandHandler<TCommand>
转换为ICommandHandler<ICommand>
,您需要知道每ICommand
TCommand
}是一个IDictionary<Type, IList>
,显然不是真的,这就是你不能进行这种转换的原因。
我现在能想到的两个解决方案都不是很好。
使字典无类型化(IDictionary<Type, object>
,甚至是IList<ICommandHandler<TCommand>>
)并转换为ICommandHandlerWrapper
,这将始终有效,因为您只需在其中添加命令类型键匹配。
制作ICommandHandler<TCommand>
,接受ICommandHandler<ICommand>
,因为它本身就是public void AddListItem<T>(T listItem)
where T : IListItem
{
_items.Add(listItem);
}
;当调用方法时,它会进行类型检查并调用基础值。
旧版
您可能遗漏了代码示例中的重要内容。
但我想问,为什么要使用
public void AddListItem(IListItem listItem)
{
_items.Add(listItem);
}
而不是
{{1}}
答案 2 :(得分:2)
从根本上说,问题在于你试图将编译时安全与你无法表达的概念混合在一起:字典将typeof(T)
映射到与T
相关的事物。在类似的情况下,我发现围绕编译时安全设计 API 很有用,但保留一小部分需要强制转换的代码。在这种情况下,只需要GetHandlers()
方法(顺便说一下,这不是线程安全的 - 我希望这没关系)。
这是完整的课程:
class GenericCommandProcessor : ICommandProcessor
{
private readonly IDictionary<Type, object> _handlers =
new Dictionary<Type, object>();
public void Register<TCommand>(ICommandHandler<TCommand> handler)
where TCommand : ICommand
{
IList<ICommandHandler<TCommand>> handlers = GetHandlers<TCommand>();
handlers.Add(handler);
}
public void Process<TCommand>(TCommand command)
where TCommand : ICommand
{
IList<ICommandHandler<TCommand>> handlers = GetHandlers<TCommand>();
foreach (var commandHandler in handlers)
{
commandHandler.Handle(command);
}
}
private IList<ICommandHandler<TCommand>> GetHandlers<TCommand>()
{
Type commandType = typeof(TCommand);
object untypedValue;
if (!_handlers.TryGetValue(commandType, out untypedValue))
{
untypedValue = new List<ICommandHandler<TCommand>>();
_handlers.Add(commandType, untypedValue);
}
return (IList<ICommandHandler<TCommand>>) untypedValue;
}
}
您可能会将字典的TValue
类型参数更改为更具限制性(例如IList
),但实际上并没有太大区别。
答案 3 :(得分:1)
我做了以下工作并且能够编译。
public interface IListItem
{
int MyProperty { get; set; }
}
public class ListProvider
{
private IList<IListItem> _items = new List<IListItem>();
public void AddListItem<T>(T listItem) where T : IListItem
{
_items.Add(listItem);
}
}
答案 4 :(得分:0)
对我来说也很好用
class Program
{
private static readonly IList<IListItem> _items = new List<IListItem>();
public static void AddListItem<T>(T listItem) where T : IListItem
{
_items.Add(listItem);
foreach (var item in _items)
{
Console.WriteLine(item.ToString());
}
Console.ReadLine();
}
static void Main(string[] args)
{
Test tester = new Test();
AddListItem(tester);
}
}
internal interface IListItem
{
}
public class Test : IListItem
{
}