c# - 使协变接口(interface)向后兼容

标签 c# .net generics interface covariance

我们有一个接口(interface)来处理 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,这导致编译时出现“无效方差”错误。

我可以通过向接口(interface)添加第二种类型参数来找到解决方案,如下所示:

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

最后,问题:如何让变化 100% 向后兼容?

我尝试过的:

  1. 添加IRepository<T>: IRepository<T,T> -> 符合,但是 RealRepository没有实现 IRepository不再。
  2. 在实现中添加 2 个接口(interface):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) , 它显然可以处理 CustomAccountEntity实例也是; OTOH如果具体方法是Save(CustomAccountEntity) , 它无法处理简单的 AccountEntity实例。

话虽如此,那我觉得你应该

  1. 改用逆变;
  2. 使用最专业的类型声明所有依赖项,例如IRepository<CustomWhateverEntity> ;
  3. 在 IoC 注册代码中,为每个特定实体设置 Repository<CustomeWhateverEntity> , 如果你需要额外的行为,或者只是 Repository<WhateverEntity>否则。

关于c# - 使协变接口(interface)向后兼容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55150924/

相关文章:

c# - 如何将文件复制到另一个路径?

c# - 异步方法调用期间主线程完成?

c# - 如何对使用 OWIN Cookie 身份验证的代码进行单元测试

.net - 什么是法师,它有什么用处?

c# - Nhibernate 析取 ("OR") 跨多对一和多对多关系的查询

c# - 无法将类型从 System.Collection.Generic.IEnumerable.MyClass<Node> 隐式转换为 MyClass<Node>

c# - 后代非泛型类到基泛型类

java - 为什么我不能初始化 Map<int, String>?

java - 使用非 Comparable 类 : why a run-time exception, 而不是编译时错误创建 TreeSet?

c# - 我可以在 Sitecore 的共享环境中运行的两个不同的 .NET MVC 站点中使用 ServiceStack 吗?