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

C# 委托类型和与泛型委托类型的协方差,c#,delegates,covariance,C#,Delegates,Covariance,我想维护一个代表列表(这里是:“mCommandHandlers”)。因为它们是泛型委托,所以我实际上定义了第二种委托类型,这样我就可以维护这样一个列表: public delegate void CommandHandler<TCommand>(TCommand command) where TCommand : ICommand; public delegate void ICommandHandler(ICommand command); Dictionary<Type,

我想维护一个代表列表(这里是:“mCommandHandlers”)。因为它们是泛型委托,所以我实际上定义了第二种委托类型,这样我就可以维护这样一个列表:

public delegate void CommandHandler<TCommand>(TCommand command) where TCommand : ICommand;
public delegate void ICommandHandler(ICommand command);
Dictionary<Type, ICommandHandler> mCommandHandlers;
public委托void CommandHandler(TCommand命令),其中TCommand:ICommand;
公共委托无效ICommandHandler(ICommand命令);
字典mCommandHandlers;
我将使用第一种类型来实现编译时优势,例如确切地知道在我的委托的实现中使用了什么类型的TCommand:

RegisterHandler<ResourceCommand>((command) =>
{
       if (command != null)
       {
            ResourceManager.ResourceReceived(command.ResourceName, command.ResourceHash, command.ResourceData);
       }
});
RegisterHandler((命令)=>
{
if(命令!=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);
}
public void RegisterHandler(CommandHandler),其中TCommand:ICommand
{
添加(typeof(TCommand),handler);
}
但我收到以下错误消息:

错误3参数2:无法从转换
CommandHandler'
“ICommandHandler”


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

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

public void RegisterHandler<TCommand>(CommandHandler<TCommand> handler)
  where TCommand : ICommand
{
  mCommandHandlers.Add(
    typeof(TCommand), 
    (command) => handler((TCommand)command);
  );
}
公共无效注册表句柄(CommandHandler)
其中TCommand:ICommand
{
mCommandHandlers.Add(
类型(t命令),
(command)=>handler((TCommand)命令);
);
}
编译器是否应该看到事实上我的第一个委托类型要求参数至少为ICommand类型,以确保委托实例也符合第二个委托类型的签名

这里有两个问题

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

其次,您得到的差异是错误的,
CommandHandler
只接受特定类型的命令。。。而
ICommandHandler
将接受任何
ICommand

因此,假设我们可以这样做:

CommandHandler<FooCommand> fooHandler = HandleFoo;
ICommandHandler generalHandler = new ICommandHandler(fooHandler);
。。。您希望
HandleFoo
方法如何处理这个问题

对于任何特定的
TCommand
,都存在从
ICommandHandler
CommandHandler
的转换,因为调用新委托时,该转换始终有效。示例代码:

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);
    }
}
使用系统;
委托void命令处理程序(TCommand命令)
其中t命令:i命令;
委托无效ICommandHandler(ICommand命令);
接口ICommand{}
类命令:ICommand{}
课堂测试
{
公共静态void Main()
{
ICommandHandler x=null;
CommandHandler y=新的CommandHandler(x);
}
}
我建议你把字典改成:

Dictionary<Type, Delegate> mCommandHandlers;
字典mCommandHandlers;

然后,当您需要调用任何特定的委托时,您将需要强制转换到正确类型的处理程序——我假设您会知道这一点,因为此时有一个类型参数。或者,您可以创建一个代理处理程序,按照Jared的回答执行转换。

谢谢,我也考虑过这种间接的方式,但我真的很想知道编译器为什么不认为它们是兼容的?因为委托应该是协变的,所有的代码都要求协变。@user1610325:请看我的答案-它们与您尝试的方式不兼容。相反,它们是相容的。始终考虑“不管参数是什么,通过新委托调用原始委托是否有效?”-编译器尝试确保始终有效。@user1610325-
Func
委托在其输出中是协变的,但在其参数中是逆变的。您的委托没有任何差异批注。@user1610325委托只有通过
in
out
参数显式标记为协变量/反变量时才是协变量。您的代理中只有一个是通用的,因此可能存在差异。但在这种特殊情况下,您只能使用逆变,因为所有值都位于输入位置。对冲方差不允许您在此处所需的转换类型(允许值更具体,而不是更一般)。乔恩的解释有一些更详细的内容,你会感兴趣的,我现在可以看到我是如何完全把协方差的想法转错方向的:)。我的特定代表不能坐在那里等待接受任何类型的ICommand,他们应该这样做,成为MCommandHandler的一部分。谢谢你指出这一点!
Dictionary<Type, Delegate> mCommandHandlers;