C#泛型类型不可分配

时间:2011-03-18 13:52:38

标签: c# generics

我想使用泛型编写 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级别,此代码无法编译。我想我只是看不到树上的木头......

非常感谢提前。

5 个答案:

答案 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

但是,在您的情况下,TCommandin参数,要将ICommandHandler<TCommand>转换为ICommandHandler<ICommand>,您需要知道每ICommand TCommand }是一个IDictionary<Type, IList>,显然不是真的,这就是你不能进行这种转换的原因。

我现在能想到的两个解决方案都不是很好。

  1. 使字典无类型化(IDictionary<Type, object>,甚至是IList<ICommandHandler<TCommand>>)并转换为ICommandHandlerWrapper,这将始终有效,因为您只需在其中添加命令类型键匹配。

  2. 制作ICommandHandler<TCommand>,接受ICommandHandler<ICommand>,因为它本身就是public void AddListItem<T>(T listItem) where T : IListItem { _items.Add(listItem); } ;当调用方法时,它会进行类型检查并调用基础值。


  3. 旧版

    Works on my machine

    您可能遗漏了代码示例中的重要内容。

    但我想问,为什么要使用

    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
{
}