变体通用接口

Variant Generic Interfaces

我有一个通用接口,还有一个 class 使用具体类型参数实现该接口。我也有一个泛型 class 使用泛型接口作为它的类型约束,但是类型参数被限制为某个基 class 的子 class。我想用实现该接口的 class 实例化通用 class,但在将 class 转换为该接口时遇到问题。以下代码说明了我提到的所有 classes:

基地class:

class DomainBase
{
}

接口中用作类型参数的class

class Person : DomainBase
{
}

通用接口:

public interface IRepository<T> where T : class
{
    IEnumerable<T> Fetch();
    T Persist(T item);
}

实现通用接口的class:

class PersonRepository : IRepository<Person>
{
    public IEnumerable<Person> Fetch()
    {
        ...
    }
    public Person Persist(Person item)
    {
        ...
    }
}

通用class使用通用接口:

class DomainBaseViewModel<Repository>
    where Repository : IRepository<DomainBase>, new()
{
    private Repository repository = new Repository();
    private ObservableCollection<DomainBase> items;
}

但是,无法编译以下行,因为 PersonRepository 无法转换为 IRepository:

var viewModel = new DomainBaseViewModel<PersonRepository>();

虽然我可以通过协方差解决这个问题,但它不允许在参数列表中使用类型参数:

public interface IRepository<out T> where T : class
{
    ...
    T Persist(object item);
}

class PersonRepository : IRepository<Person>
{
    public Person Persist(object item)
    {
        ...
    }
}

所以我必须将参数转换为 Person,这会损害类型安全。

在这种情况下,是否有更好的方法允许协变和在参数列表中使用类型参数?

否 - 对协方差的限制的全部意义在于它保证了安全性。一个 PersonRepository 不是 一个 IRepository<DomainBase> 因为你不能要求它持久化任何任意的 DomainBase 对象。您希望这段代码做什么?

class Product : DomainBase {}
...
IRepository<DomainBase> repository = new PersonRepository();
repository.Persist(new Product());

PersonRepository 不知道如何保留 Product 值。

如果在某些情况下您只需要存储库界面的 "read" 部分,您总是可以显式调用它:

public interface IRepositoryReader<out T>
{
    IEnumerable<T> Fetch();
}

public interface IRepository<T> : IRepositoryReader<T>
{
    T Persist(T item);
}

那么你的 DomainBaseViewModel class 可能是:

class DomainBaseViewModel<TRepository>
    where TRepository : IRepositoryReader<DomainBase>, new()
{
    private TRepository repository = new TRepository();
    private ObservableCollection<DomainBase> items;
}

如果您希望 DomainBaseViewModel 也保留项目,那将不起作用。在那种情况下,也许它在模型类型中也应该是通用的:

class DomainBaseViewModel<TRepository, TEntity>
    where TRepository : IRepository<TEntity>, new()
{
    private TRepository repository = new Repository();
    private ObservableCollection<TEntity> items;
}

然后:

var viewModel = new DomainBaseViewModel<PersonRepository, Person>();