我有一个自定义消息传递系统,我试图转换它,因此它可以支持插件。
该系统有生产者和消费者。 两者都必须指定它们产生或消费的消息类型。
对于消费者来说,要实施的接口是
IConsumer<AMessage>
我在插件dll中有一个消费者,它实现了这个接口:
FileProcessor : IConsumer<FileMessage>
用
FileMessage : AMessage
而AMessage是一个抽象类。 IConsumer和AMessage都在主(Core)程序集中。
现在,在核心中,它加载了插件并扫描了&amp;找到了我想将消费者链接到消息系统的消费者。
为了简化这里的事情,我只是尝试将消费者放在一个变量中:
IConsumer<AMessage> consumer = IConsumer<AMessage> new FileProcessor();
我在VS中收到警告:
可疑演员:解决方案中没有继承的类型 来自'TestPlugin.FileProcessor'和'Core.IConsumer'。
当我执行时,我得到了
System.InvalidCastException:无法转换类型的对象 'TestPlugin.FileProcessor'输入'Core.IConsumer`1 [Core.AMessage]'。
为什么这个演员会失败?我该如何解决这个问题?
- EDIT1 -
根据评论,这里是IProducer和IConsumer的完整定义:
public interface IProducer<out T> : IProcessor where T : AMessage
{
event MessageSender<T> SendMessage;
}
public interface IConsumer<in T> : IProcessor where T : AMessage
{
ProcessResult ProcessBean(T bean);
}
当生产者在系统中链接时,他们定义T,之后只有具有相同类型T的消费者可以链接到该生产者。 例如:
var channel = flow.From(FileProducer).To(FileConsumer);
使用flow.From:
public Channel<T> From<T>(IProducer<T> producer) where T : AMessage
{
...
}
和channel.To:
public class Channel<T> where T : AMessage
{
public Channel<T> To(IConsumer<T> consumer){
...
}
}
因此,生产者为消费者定义了T的类型。
- EDIT1 -
- EDIT2 -
需要进行向下转换是因为我正在尝试从Core系统中的JSON定义重建通道。
所以消费者(和生产者)是从插件程序集动态构造的,并添加到临时字典中(当检测到并实例化时):
var consumers = new Dictionary<string, IProcessor>();
我不能在这里为消费者使用IConsumer,因为对所有消费者而言,T不一定相同。
然后在构建频道时,我将通过它的id(字典的键)查找消费者,并将其提供给频道的To(...)方法。这失败是因为字典保存了IProcessor,动态构造的频道的“To”签名为public Channel<T> To(IConsumer<T> consumer)
。
- EDIT2 -
- 溶液 -
感谢Luaan(见评论)我找到了以下解决方案:
在扫描插件程序集时,我最初将检测到的生产者和消费者存储在<string, IProcessor>
的字典中,现在我将此字典更改为<string, dynamic>
,这解决了这个转换问题!
感谢所有加入此解决方案的人!
@Luaan,我希望我能选择你的答案作为解决方案,但它正在发表评论......
- 溶液 -
谢谢!
答案 0 :(得分:2)
您需要使用out
关键字将您的界面标记为covariant:
IConsumer<out AMessage>
这意味着您可以传递比指定类型更多的派生类型,因此您可以使用从FileMessage
派生的AMessage
。
看到你的编辑,你不需要协方差而是逆变(IConsumer<in AMessage>
)但是这个演员:
IConsumer<AMessage> consumer = (IConsumer<AMessage>)new FileProcessor();
无效。来自msdn:
逆变 使您可以使用比最初指定的更通用(更少派生)的类型。 您可以指定IEnumerable&lt; Base&gt;的实例到IEnumerable&lt; Derived&gt;类型的变量。
答案 1 :(得分:0)
如果在IConsumer<T>
界面中T
仅出现在返回值中,那么您可以使界面协变(在.NET 4及更高版本中):
public interface IConsumer<out T>
在这种情况下,此初始化将起作用:
IConsumer<AMessage> consumer = new FileProcessor();
但是如果方法参数中也使用了T
,那么你的界面就不能协变。哪个好,请参阅IList<T>
,例如:如果允许IList<Animal> cats = new List<Cat>()
,则可以调用cats.Add(new Dog())
,这是不可取的。 :)