接受两个接口之一

时间:2017-12-17 16:43:56

标签: c# .net

问题

我有两个接口IImporterIExporter以及一个处理这些接口实例的类:

class IoManager
{
    public ReadOnlyCollection<IImporter> Importers { get; } 
    public ReadOnlyCollection<IExporter> Exporters { get; }

    // ...
}

在本课程中,我想实现一个可以添加导入器或导出器的方法Add(...)

我知道我可以为两个接口创建一个重载,比如

public void Add(IExporter exporter);
public void Add(IImporter importer);

问题是如果我想通过CsvIo : IImporter, IExporter函数添加一个实现两个接口(如Add(...))的对象,我会得到一个不明确的调用编译错误,因为两个重载都接受CsvIo

我不想做什么

  • 我不想将重载重命名为不同的名称,因为大多数类型都实现了两个接口,我希望实现IExporterIImporter或两者的类型的连贯行为。
  • 我不想创建由两个接口实现的“父”接口,因为这看起来像 hacky 解决方案
  • 我不想在Add(...)调用中投射类型,例如Add((IImporter) CsvIoInstance),因为这表明只使用了此类型的导入器。但是两个重载都检查对象是否实现了两个接口并相应地将它添加到两个列表中。

问题

有没有办法以连贯的方式接受不同的类型(这样每个类型的调用看起来都一样)?

修改

澄清我的实现:这是插件系统的一部分。我想添加多种方式来存储我的数据(以便其他应用程序可以使用它)。 IExporter接口用于存储数据,而IImporter接口加载数据。 IoManager管理所有已注册的插件。当然,一个类可以(并且可能是常见的)实现某种文件格式的导出和导入。这就是为什么我希望Add(...)函数在所有情况下都相似。

然而,再次考虑它(正如评论中已经说明的那样)插件只在运行时才知道,这使得模糊性成为我认为的过时问题。所以这只适用于内置类型。

3 个答案:

答案 0 :(得分:5)

我想特别关注一句话:

  

我不想在Add(...)调用中投射类型,例如Add((IImporter) CsvIoInstance),因为这表明只使用了此类型的导入器。但是两个重载检查对象是否实现了两个接口并相应地将它添加到两个列表

您当前的设计已经放弃了类型安全的目标之一:避免运行时类型检查。理想情况下,采用IImporter参数的方法应将该参数用作IImporter。这形成了一种&#34;合同&#34;在方法的调用者和实现者之间:实现者相信调用者将提供它所需要的东西,并且调用者相信实现者只会以这种方式使用它。 (另见:Principle of least astonishment。)

想象一下,你是这个班级的消费者。有一组IImporters和一个带有签名Add(IImporter foo)的方法。你期望这种方法做什么? 希望它将foo添加到IImporters列表中 - 我会期望它也会做其他事情因为我的对象发生了实现另一个接口(例如IExporter)。以这种方式使用类型系统&#34;背叛&#34;您与来电者建立的合同。

将您的方法名称更改为AddImporterAddExporter,然后让每个方法将参数添加到相应的列表中。这解决了重载解析的问题,并且意味着调用者正在获得他们所要求的内容。

答案 1 :(得分:2)

这个问题有一个明显的解决方案,但你可以用这句话来排除它:

  

我不想将重载重命名为不同的名称,因为大多数类型都实现了两个接口,并且我希望实现IExporter,IImporter或两者的类型具有连贯的行为。

问题是你想要这样做:

public void Add(IExporter exporter);
public void Add(IImporter importer);

但你不想这样做:

public void AddExporter(IExporter exporter);
public void AddImporter(IImporter importer);

为什么呢?您可以将第一个称为“重载方法”,从技术上讲,它实际上只是两个具有相同名称的方法。

重载是指你有一个方法做一件事,但你可以使用不同的参数集。这两种方法实际上做了两件事。一个添加IExporter,一个添加IImporter。因此,对于做出不同事物的不同方法赋予不同的名称并没有什么“不连贯”。为了操作编译器而为类添加额外的接口和复杂性会更糟糕得多。

答案 2 :(得分:1)

它类似于你没有父接口的请求,但是你应该拥有的是一个结合了两者的子接口。

public interface IImporterExporter : IImporter, IExporter {
}

在您的类而不是两个单独的接口上实现它:

class ImporterExporter : IImporterExporter {
}

然后编写第三个重载:

public void Add(IImporterExporter importerExporter) {
   // decide what to do if both interfaces are implemented
}

有一个原因可能会导致模糊问题,这是因为任何一个重载都可能是正确的,或者甚至两个都是同时正确的。您需要通过决定在这种情况下做什么来解决歧义。由于C#没有类型联合,因此必须声明一个单独的接口来统一它们。

请注意,这是“hacky”,但实际上是将简单的界面组合成更复杂的界面的好设计。这样可以实现更清晰,更精确的代码,这是一件好事。但无论如何,即使你不喜欢出于某种原因组合接口的想法,在C#中仍然需要它来避免这个确切的问题。

如果没有第三个界面,你剩下的就是寻找两种类型共有的其他东西,而没有父界面,你基本上只剩下object。这将允许单一方法,但您将失去类型系统的好处。

public void Add(object obj) {
   var maybeImporter = obj as IImporter;
   var maybeExporter = obj as IExporter;

   if (maybeImporter != null && maybeExporter != null) {
      // do something if both are implemented
   } else if (maybeImporter != null) { 
      // importer only
   } else
      // exporter only
   }
}