如何在C#中正确设计与非泛型对应的通用接口?

时间:2013-10-17 13:06:17

标签: c# generics interface covariance

我有以下代码段:

 interface IRepositoryBase<BackupType> where BackupType : IBackup {
    IEnumerable<BackupType> Backups { get; set; }
    void Delete(BackupType backup);
    BackupType GetOrCreateBackup(IFileSource source);
  }

  interface IRepository : IRepositoryBase<IBackup> {
  }

  interface IRepository<BackupType> : IRepository, IRepositoryBase<BackupType> where BackupType : IBackup {
  }

基本上我的请求是我希望能够替换任何IRepository&lt; BackupType&gt;对于IRepository,如果我想放一些IRepository&lt; BackupType&gt;到集合(所以我可以指定集合的​​类型)。除此之外,假设IRepository&lt; BackupType&gt;似乎是合理和合乎逻辑的。继承自IRepository(而不是相反)。另外,我绝对希望IRepository拥有IRepository&lt; BackupType&gt;的所有属性和方法。 (唯一的区别是非通用与通用)

不幸的是,编译器在代码中给出了以下错误:

IRepository<BackupType> cannot implement both IRepositoryBase<IBackup> and IRepositoryBase<BackupType> because they may unify for some type parameter substitutions

所以我再尝试一些其他代码来消除这个错误(IRepositoryBase&lt;&gt;接口消失了):

  interface IRepository {
    IEnumerable<IBackup> Backups { get; set; }
    void Delete(IBackup backup);
    IBackup GetOrCreateBackup(IFileSource source);
  }

  interface IRepository<BackupType> : IRepository where BackupType : IBackup {
    new IEnumerable<BackupType> Backups { get; set; }
    void Delete(BackupType backup);
    new BackupType GetOrCreateBackup(IFileSource source);
  }

现在我有这些丑陋的“新”。此外,在实现IRepository&lt; BackupType&gt;时,编译器似乎仍然希望实现两个“备份”属性,尽管我用“新”隐藏了第一个属性。

有人可以告诉我如何正确地做到这一点吗? :)

3 个答案:

答案 0 :(得分:3)

如果你看一下Microsoft如何实现List<T>,你会发现他们没有继承该接口。将List<T>同时视为IListIList<T>的唯一原因是因为List<T>实现了这两者。

您可能希望实现非通用接口显式。以下是隐式和显式实现之间的区别:

// Implicit
public IEnumerable<TapeBackup> Types { get; set; }

// Explicit
IEnumerable<IBackup> IRepository.Types { get; set; }

显式接口实现隐藏在程序集元数据中且无法访问,除非您首先将实例强​​制转换为适当的接口类型。您只需将显式实现换行并调用隐式实现,因为类型很可能是兼容的。

但我认为你不能实现只为两个接口实现一次成员的目标。

答案 1 :(得分:0)

这是你想要达到的目标吗?

internal class BackupType : IBackup { }

    internal interface IFileSource { }

    internal interface IBackup { }

    interface IRepository<TBackup> where TBackup : IBackup
    {
        IEnumerable<TBackup> Backups { get; set; }
        void Delete(TBackup backup);
        TBackup GetOrCreateBackup(IFileSource source);
    }

    interface IRepository : IRepository<IBackup> { }
    interface IRepositoryBackupType : IRepository<BackupType> { }

    class RepositoryBackupType:IRepository,IRepositoryBackupType
    {
    ... (implementation of both interfaces)
    }

但是如果你有一个实现两个接口的类,你必须明确地实现它们,因为我不相信类型系统能够推断底层集合的转换。

列表与LT; IBACKUP&GT;可以保存所有IBackup和BackupType,但是在这种情况下,您不能简单地将其转换为IEnumerable&lt; BackupType&gt; (因为List中可能有BackupType2类型,无法转换为BackupType) 列表与LT; BackupType&GT;可以安全地转换为IEnumerable&lt; IBackup&gt;,但是你不能只为它添加IBackup(出于与上述相同的原因)。

答案 2 :(得分:0)

如果你选择:

  interface IRepositoryBase<TBackup> where TBackup : IBackup 
  {
    IEnumerable<TBackup> Backups { get; set; }
    void Delete(TBackup backup);
    TBackup GetOrCreateBackup(IFileSource source);
  }

  interface IRepository
  {
    IEnumerable<IBackup> Backups { get; set; }
    void Delete(IBackup backup);
    IBackup GetOrCreateBackup(IFileSource source);
  }

  interface IRepository<TBackup> : IRepositoryBase<TBackup>, IRepository where TBackup : IBackup 
  {
  }

编译器不会抱怨。

您仍然需要每次实现六个成员而不是三个成员,其中一些是通过显式接口实现。我没有解决方法。

当您实施genereic IEnumerable<>时必须提供两个GetEnumerator方法,因为您还继承了非通用IEnumerable