使协变接口向后兼容

时间:2019-03-13 20:44:59

标签: c# .net generics interface covariance

我们有一个非常简单的定义来处理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个接口:public class RealRepository<TBase, TChild>: IRepository<TBase, TChild>, IRepository<TChild>,但这会产生修饰错误“无法同时实现...和...,因为它们可能会为某些类型参数替换统一”

1 个答案:

答案 0 :(得分:1)

Save(T document)在{em> convarivariant 位置具有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),则显然也可以处理CustomAccountEntity实例; OTOH如果具体方法是Save(CustomAccountEntity),则将无法处理简单的AccountEntity实例。

话虽如此,我想你应该

  1. 改用相反性;
  2. 使用最专业的类型声明所有依赖项,例如IRepository<CustomWhateverEntity>;
  3. 在IoC注册代码中,为每个特定实体设置Repository<CustomeWhateverEntity>(如果您需要额外的行为),否则设置为Repository<WhateverEntity>