【问题标题】:Making covariant interface backward compatible使协变接口向后兼容
【发布时间】:2019-03-13 20:44:59
【问题描述】:

我们有一个接口来处理具有非常简单定义的 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&lt;T&gt;: IRepository&lt;T,T&gt; -> 符合要求,但 RealRepository 不再实现 IRepository
  2. 在实现中添加 2 个接口:public class RealRepository&lt;TBase, TChild&gt;: IRepository&lt;TBase, TChild&gt;, IRepository&lt;TChild&gt; 但这会导致编译错误“无法同时实现 ... 和 ...,因为它们可能会针对某些类型参数替换进行统一”

【问题讨论】:

    标签: c# .net generics interface covariance


    【解决方案1】:

    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&lt;CustomAccountEntity&gt; 时,您可以使用具体的 EntityRepository&lt;AccountEntity&gt; 实例。看似违反直觉,但实际上完全正确:如果具体方法是Save(AccountEntity),它显然也可以处理CustomAccountEntity 实例; OTOH,如果具体方法是Save(CustomAccountEntity),它将无法处理简单的AccountEntity 实例。

    说了这么多,那我觉得你应该

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

    【讨论】:

    • IQueriable 继承自 IEnumerable,并将 IEnumerable 定义为 Covariant:公共接口 IEnumerable : System.Collections.IEnumerable
    • 很抱歉给您带来了困惑。你是在说System.Linq.IQueryable吗?所以它是 Y,而不是 I。由于拼写的不同,我假设你的 IQueriable 是一个自定义的、用户制作的界面(然后我实际上在我的示例中使用了 IQueryable,这是有效的,但我猜测没有帮助)。无论如何,底线是:如果您实际上是在实现 System.Linq.IQueryable,并且您想要这样做,那么您就不走运了:IRepository&lt;T&gt; 不能同时是协变和逆变的。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多