【问题标题】:Dictionary of types and actions with a generic parameter具有泛型参数的类型和操作字典
【发布时间】:2018-04-13 00:39:08
【问题描述】:

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

服务应保留dictionary<type, Action<T>>,以便查找如何“处理”特定消息。

基本上,我有:

    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>'

如果不清楚;消息“到达”为包含“类型代码”的byte[]。从“类型代码”中,我得到了Type应该然后能够查找到合适的Action 来获取。 Action 显然应该将TMessage 作为参数。

我似乎无法就编译器为何给我这个错误得出一个合乎逻辑的结论,尽管我怀疑这与 DictionaryTMessage 不在同一上下文中有关。

提前致谢!

【问题讨论】:

  • 我不确定你到底想在这里完成什么,但将TMessage 限制为IMessage 感觉不对。
  • @P.Brian.Mackey TBH 我宁愿不使用 IMessage。除了使用private Dictionary&lt;Type, Action&lt;Object&gt;&gt; subActions;,我想不出任何其他方法,我宁愿避免将Object 转换为Tmessage

标签: c# generics delegates action


【解决方案1】:

让我们考虑这些方法:

public void MessageMethodInterface(IMessage arg)
{

}
public void MessageMethodClass(MyMessage arg)
{

}

其中MyMessage 是一个实现IMessage 接口的类。

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

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

SomeOtherMessage 实现IMessage。这将起作用。我们有一个Action,它将IMessage 作为输入。由于 SomeOtherMessage 实现了 IMessage,因此它可以编译并工作。

现在让我们创建另一个Action

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

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

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

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

更多关于contravariance of Action type is here

【讨论】:

  • 我明白了,这是有道理的。我尝试这种方式的原因是避免将 IMessage 强制转换为 actual 所需的消息类型。我知道在每个Actions 中添加(ActualMessageType) message 没什么大不了的,但是有什么简单的方法可以避免强制转换吗?
  • 我不确定我是否理解强制转换的解决方案。即使您强制该操作采用您的方法 b/c 您已将其转换为预期类型,但在执行您的处理程序时将接受 IMessage 输入,因为这是字典中的签名。我想不出一个干净、通用的解决方案来解决您的问题……但是您所看到的原因与答案中的一样。起初,逆变听起来有点反直觉,但当你分析它时,它实际上可以防止类型不安全的行为。
  • 好的。我将不得不回答另一个我认为的问题......这个措辞是否有意义:How to implement a collection of type constrained generic Actions (Action where T : Type)?
  • 我没有一个简洁的解决方案。也许是基于object:Dictionary&lt;Type, Action&lt;object&gt;&gt; subActions 的集合。然后是采用objectSubscribe 方法。像这样public void Subscribe&lt;T&gt;(Action&lt;object&gt; action)。但这远非理想的解决方案。
【解决方案2】:

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

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));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-04
    • 2018-04-16
    • 1970-01-01
    相关资源
    最近更新 更多