委托类型和与泛型委托类型的协方差

本文关键字:类型 方差 泛型 | 更新日期: 2023-09-27 18:28:27

我想维护一个委托列表(此处:"mCommandHandlers")。由于它们是通用委托,我实际上定义了第二种类型的委托,这样我就可以维护这样一个列表:

public delegate void CommandHandler<TCommand>(TCommand command) where TCommand : ICommand;
public delegate void ICommandHandler(ICommand command);
Dictionary<Type, ICommandHandler> mCommandHandlers;

我会使用第一种类型来获得编译时的优势,例如确切地知道在我的委托的实现中使用了什么类型的TCommand:

RegisterHandler<ResourceCommand>((command) =>
{
       if (command != null)
       {
            ResourceManager.ResourceReceived(command.ResourceName, command.ResourceHash, command.ResourceData);
       }
});

在RegisterHandler中,我现在想做以下操作:

public void RegisterHandler<TCommand>(CommandHandler<TCommand> handler) where TCommand : ICommand
{
      mCommandHandlers.Add(typeof(TCommand), handler);
}

但我收到以下错误消息:

错误3参数2:无法从转换CommandHandler<TCommand>''ICommandHandler'

为什么会这样?编译器是否应该看到,事实上,我的第一个委托类型要求参数至少为ICommand类型,以确保委托实例也符合第二个委托类型的签名?

委托类型和与泛型委托类型的协方差

问题是这两种委托类型根本不兼容。为了实现这一点,您需要添加一个间接层,用于转换ICommandTCommand之间的参数。

public void RegisterHandler<TCommand>(CommandHandler<TCommand> handler)
  where TCommand : ICommand
{
  mCommandHandlers.Add(
    typeof(TCommand), 
    (command) => handler((TCommand)command);
  );
}

编译器是否应该看到,事实上,我的第一个委托类型要求参数至少为ICommand类型,以确保委托实例也符合第二个委托类型的签名?

这里有两个问题。

首先,委托差异不允许将一种委托类型隐式引用转换为另一种,它允许您从兼容的现有委托实例创建新的委托实例。

其次,您得到了错误的方差,CommandHandler<TCommand>将只接受特定的类型的命令。。。而CCD_ 6将接受任何CCD_。

所以假设我们可以这样做:

CommandHandler<FooCommand> fooHandler = HandleFoo;
ICommandHandler generalHandler = new ICommandHandler(fooHandler);

然后我们可以呼叫:

generalHandler(new BarCommand());

您希望HandleFoo方法如何应对这种情况?

对于任何特定的TCommand都是从ICommandHandlerCommandHandler<TCommand>的转换,因为当调用新的委托时,它总是有效的。样本代码:

using System;
delegate void CommandHandler<TCommand>(TCommand command)
    where TCommand : ICommand;
delegate void ICommandHandler(ICommand command);
interface ICommand {}
class Command : ICommand {}
class Test
{
    public static void Main()
    {
        ICommandHandler x = null;
        CommandHandler<Command> y = new CommandHandler<Command>(x);
    }
}

我建议你把字典改成:

Dictionary<Type, Delegate> mCommandHandlers;

然后,当您需要调用任何特定的委托时,您需要转换到正确类型的处理程序——我想您会知道这是由于当时的类型参数。或者,您可以根据Jared的回答创建一个执行强制转换的代理处理程序。