具有泛型参数的类型和动作字典

Dictionary of types and actions with a generic parameter

我正在尝试使用 MessagePack 获得一个简单的消息传递服务。

该服务应保留 dictionary<type, Action<T>> 以便查找如何 "deal" 使用特定消息。

基本上,我有:

    private Dictionary<Type, Action<IMessage>> subActions;

    public void Subscribe<TMessage>(Action<TMessage> action) where TMessage : IMessage
    {
        subActions.Add(typeof(TMessage), action);
    }

我得到:

Argument 2: cannot convert from 'System.Action<TMessage>' to 'System.Action<GameServer.Messages.IMessage>'

万一不清楚;消息 "arrive" 作为包含 "typecode" 的 byte[]。从 "typecode" 我得到 Type,然后 应该 可以查找合适的 Action 来接受。显然,Action 应该将 TMessage 作为参数。

关于编译器为什么给我这个错误,我似乎无法得出合乎逻辑的结论,尽管我怀疑它与 Dictionary 不在与 [= 相同的上下文中有关17=].

提前致谢!

让我们考虑这些方法:

public void MessageMethodInterface(IMessage arg)
{

}
public void MessageMethodClass(MyMessage arg)
{

}

其中 MyMessage 是实现 IMessage 接口的 class。

现在让我们创建一个 Action 并使用它们:

var action = new Action<IMessage>(MessageMethodInterface);
action(new SomeOtherMessage());

SomeOtherMessage 实施 IMessage。这会起作用。我们有一个 ActionIMessage 作为输入。由于 SomeOtherMessage 实现了 IMessage,它可以编译并运行。

现在让我们创建另一个 Action:

var action2 = new Action<IMessage>(MessageMethodClass);
action(new SomeOtherMessage());

这行不通:我们创建了一个 Action,它应该接受任何类型 IMessage 的对象,但我们分配给它的方法只能处理类型 MyMessage 的输入] class - 因此我们使用我们提供的方法缩小了操作的范围 - 它不会编译。

您的代码中发生的是场景 2。您希望字典包含可以处理 IMessage 的方法,但您传入的方法只能处理特定类型。这不是类型安全的。即使 class 实现了接口,方法本身也只处理特定类型,而不是该接口的每种类型。与 MyMessageSomeOtherMessage.

的示例相同

这是Action类型的逆变。
基本类型的 Action 可以分配派生类型的操作。这是类型安全的 b/c 仅使用基类型的方法将知道如何处理派生类型。反之则不然。以 object 作为输入的方法将处理 string。由于 string 公开了 object 的所有方法和属性。但反之则不然。

更多关于 contravariance of Action type is here

它不起作用,因为你想像这样分配:

Action<IMessage> dest = new Action<TMessage>(x => x.TMessageProp = 123);

所以,如果编译器允许的话,你可以进一步写,例如:

dest(new TAnotherMessage());

因为 TAnotherMessage 也实现了 IMessage。但它会导致异常,因为 dest 只能与 TMessage 一起使用。阅读有关逆变的更多信息。

您可以保持类型安全和 fix it safely,因为尽管如此您知道类型,您可以通过字典的键将其传递给特定的操作:

public void Subscribe<TMessage>(Action<TMessage> action) where TMessage : IMessage
{
    subActions.Add(typeof(TMessage), x => action((TMessage)x));
}