I have a generic interface, and a class implementing that interface with a concrete type parameter. I also have a generic class using the generic interface as its type constraint, but the type parameter is restricted to be a subclass of a certain base class. I want to instance the generic class with the class implementing that interface but have a problem of converting the class to that interface. The following code illustrates all the classes I mentioned:
The base class:
class DomainBase
{
}
The class used as the type parameter in the interface
class Person : DomainBase
{
}
The generic interface:
public interface IRepository<T> where T : class
{
IEnumerable<T> Fetch();
T Persist(T item);
}
The class implementing the generic interface:
class PersonRepository : IRepository<Person>
{
public IEnumerable<Person> Fetch()
{
...
}
public Person Persist(Person item)
{
...
}
}
The generic class using the generic interface:
class DomainBaseViewModel<Repository>
where Repository : IRepository<DomainBase>, new()
{
private Repository repository = new Repository();
private ObservableCollection<DomainBase> items;
}
However, the following line can't get compiled because PersonRepository is unable to be converted to IRepository<DomainBase>:
var viewModel = new DomainBaseViewModel<PersonRepository>();
Although I can solve this issue by covariance but it disallows the use of the type parameter in parameter lists:
public interface IRepository<out T> where T : class
{
...
T Persist(object item);
}
class PersonRepository : IRepository<Person>
{
public Person Persist(object item)
{
...
}
}
So I have to convert the parameter to Person, which compromises type safety.
Is there a better way to allow covariance and the use of type parameter in parameter lists in this case?
No - the whole point of the restriction on covariance is that it guarantees safety. A PersonRepository
isn't an IRepository<DomainBase>
because you can't ask it to persist any arbitrary DomainBase
object. What would you expect this code to do?
class Product : DomainBase {}
...
IRepository<DomainBase> repository = new PersonRepository();
repository.Persist(new Product());
PersonRepository
doesn't know how to persist Product
values.
If in some cases you only need the "read" parts of the repository interface, you could always call that out explicitly:
public interface IRepositoryReader<out T>
{
IEnumerable<T> Fetch();
}
public interface IRepository<T> : IRepositoryReader<T>
{
T Persist(T item);
}
Then your DomainBaseViewModel
class could be:
class DomainBaseViewModel<TRepository>
where TRepository : IRepositoryReader<DomainBase>, new()
{
private TRepository repository = new TRepository();
private ObservableCollection<DomainBase> items;
}
That doesn't work if you want your DomainBaseViewModel
to persist items as well though. In that case, perhaps it should be generic in the type of model as well:
class DomainBaseViewModel<TRepository, TEntity>
where TRepository : IRepository<TEntity>, new()
{
private TRepository repository = new Repository();
private ObservableCollection<TEntity> items;
}
Then:
var viewModel = new DomainBaseViewModel<PersonRepository, Person>();
See more on this question at Stackoverflow