cdbeelala89
cdbeelala89

Reputation: 2146

How to design generic interface with its non-generic counterpart correctly in C#?

I have the following code snippet:

 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 {
  }

Basically the request I have is I want to be able to substitute any IRepository<BackupType> for IRepository, in case I want to put some IRepository<BackupType> into a collection (so I can specify a type for the collection). I addition to that it seems reasonable and logical to assume IRepository<BackupType> inherits from IRepository (and not the other way around). Also I definitely want IRepository to have all the properties and methods of IRepository<BackupType> (the only difference being non-generic vs generic)

Unfortunately, the compiler gives the following error with the code:

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

So I try again with some other code which eliminates this error (IRepositoryBase<> interface is gone):

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

Now I have these ugly "new"'s in there. Futhermore, upon implementing IRepository<BackupType>, the compiler still seems to want both "Backups" properties implemented, although I hid the first one with "new".

Can somebody tell me how to do this properly? :)

Upvotes: 2

Views: 184

Answers (3)

Trevor Elliott
Trevor Elliott

Reputation: 11252

If you look at how Microsoft implemented List<T> you'd see that they did not inherit the interface. The only reason you can treat a List<T> as both an IList and a IList<T> is because List<T> implements both of them.

What you probably want is to implement the non-generic interface explicitly. Here is the difference between an implicit and explicit implementation:

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

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

Explicit interface implementation is hidden in the assembly metadata and inaccessible unless you first cast the instance to the appropriate interface type. You would just have your explicit implementations wrap and call the implicit ones since the types are most likely compatible.

But I don't think you can achieve your goal of only implementing a member once for two interfaces.

Upvotes: 3

Jeppe Stig Nielsen
Jeppe Stig Nielsen

Reputation: 61952

If you go with:

  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 
  {
  }

the compiler won't complain.

You will still have to implement six members instead of three everytime, some of them by explicit interface implementation. I have no solution for that.

It is the same when you implement genereic IEnumerable<> and have to supply two GetEnumerator methods because you also inherit non-generic IEnumerable.

Upvotes: 0

milanio
milanio

Reputation: 4232

Is this what you want to achieve?

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

But if u will have a class which implements both interfaces, you have to implement them explicitly as I don't believe type system will be able to infer conversions for underlying collection.

List<IBackup> can hold all IBackup and BackupType, however in that case you cannot just simple cast it to IEnumerable<BackupType> (as there is maybe BackupType2 type in the List which cannot be converted to BackupType) List<BackupType> can be safely converted to IEnumerable<IBackup>, however you cannot just add IBackup to it (for the same reason as above).

Upvotes: 0

Related Questions