使协变接口向后兼容

问题描述 投票:2回答:1

我们有一个处理DAL的界面,定义非常简单:

interface IRepository<T> : IQueriable<T> // so we can read data from database
{
   Save(T document); // dozen of methods here
} 

大多数情况下,我们使用两种实现:实际版本和内存版本用于单元测试。这是一个类的声明:

public RealRepository : IRepository<AccountEntity> { ... } 
// typical IOC usage
services.AddSingleton<IRepository<AccountEntity>, RealRepository<AccountEntity>>();

现在我们正在努力将主代码库分拆为自定义版本的项目,我们需要数据库中的自定义字段和存储库中的偶然自定义行为。大多数类都适用于基本实现,但其他类需要特定的实现。所以我的目标是获得以下服务:

var repository = new RealRepository<CustomAccountEntity>();
services.AddSingleton(IRepository<AccountEntity>, repository);
// for new classes
services.AddSingleton(IRepository<CustomAccountEntity>, repository);

我试图将out T添加到IRepository,但我在输入参数中使用T,这给编译时间“无效方差”错误。

我可以通过向接口添加第二个类型参数来看到解决方案,所以它看起来像:

IRepository<TBase, out TChild> : IQueriable<TChild> {
    Save (T document);
}

最后,问题:如何使100%向后兼容变更?

我尝试了什么:

  1. 添加IRepository<T>: IRepository<T,T> - >遵守,但RealRepository不再实施IRepository
  2. 在实现中添加2个接口:qazxsw poi但是这给出了编译错误'无法同时实现...和...因为它们可能统一某些类型参数替换'
c# .net generics interface covariance
1个回答
1
投票

public class RealRepository<TBase, TChild>: IRepository<TBase, TChild>, IRepository<TChild>在逆境中有Save(T document)。这意味着T,而不是in T

让我们回顾一下逆变意味着什么。假设你有这个代码:

out T

using System; public class Entity {} public class AccountEntity : Entity {} public class CustomAccountEntity : AccountEntity {} public interface IQueryable<in T> where T : Entity {} public interface IRepository<in T> where T : Entity { void Save(T record); } public class EntityRepository<T> : IRepository<T> where T : Entity { public void Save(T record) {} } public class Program { public static void Main() { // This is ***VALID***: IRepository<CustomAccountEntity> repo = new EntityRepository<AccountEntity>(); Console.WriteLine(repo == null ? "cast is invalid" : "cast is valid"); } }

因此,无论何时需要https://dotnetfiddle.net/cnEdcm,您都可以使用具体的IRepository<CustomAccountEntity>实例。看似违反直觉,但它实际上是完全正确的:如果具体方法是EntityRepository<AccountEntity>,它显然也可以处理Save(AccountEntity)实例; OTOH如果具体方法是CustomAccountEntity,它将无法处理简单的Save(CustomAccountEntity)实例。

话虽如此,我认为你应该这样做

  1. 改为使用逆变;
  2. 使用最专业的类型声明所有依赖项,例如AccountEntity;
  3. 在IoC注册码中,对于每个特定实体,如果需要额外行为,则设置IRepository<CustomWhateverEntity>,否则设置为Repository<CustomeWhateverEntity>
© www.soinside.com 2019 - 2024. All rights reserved.