【问题标题】:Covariance confusion. Can't assign tuples of implemented interfaces to list of tuples协方差混淆。无法将已实现接口的元组分配给元组列表
【发布时间】:2017-01-06 11:57:51
【问题描述】:

前言:我知道有很多关于协变和逆变的问题和答案,但我仍然感到困惑,不知道要实施什么解决方案。

我有两个接口,它们的实现打算成对一起使用。一个提供有关销售项目的信息,另一个提供销售项目的语言相关信息。

我无法控制这些界面

public interface IItem
{
    decimal Price { get; set; }
}

public interface IItemTranslation
{
    string DisplayName { get; set; }
}

对于有形的GoodsItem 和无形的ServiceItem,我也有这两个接口的两个实现。同样,我无法控制这些界面

public class GoodsItem : IItem
{
    public decimal Price { get; set; } //implementation
    public float ShippingWeightKilograms { get; set; } //Property specific to a GoodsItem
}

public class GoodsTranslation : IItemTranslation
{
    public string DisplayName { get; set; } //implementation
    public Uri ImageUri { get; set; } //Property specific to a GoodsTranslation
}


public class ServiceItem : IItem
{
    public decimal Price { get; set; } //implementation
    public int ServiceProviderId { get; set; } // Property specific to a ServiceItem
}

public class ServiceTranslation : IItemTranslation
{
    public string DisplayName { get; set; } //implementation
    public string ProviderDescription { get; set; } // Property specific to a ServiceTranslation
}

正如我所说,这些是我无法控制的类。我想创建这些配对的通用列表 (List<Tuple<IItem, IItemTranslation>>),但我不能:

public class StockDisplayList
{
    public List<Tuple<IItem, IItemTranslation>> Items { get; set; }

    public void AddSomeStockItems()
    {
        Items = new List<Tuple<IItem, IItemTranslation>>();

        var canOfBeans = new Tuple<GoodsItem, GoodsTranslation>(new GoodsItem(), new GoodsTranslation());

        var massage = new Tuple<ServiceItem, ServiceTranslation>(new ServiceItem(), new ServiceTranslation());

        Items.Add(canOfBeans); //illegal: cannot convert from 'Tuple<GoodsItem, GoodsTranslation>' to 'Tuple<IItem, IItemTranslation>'
        Items.Add(massage); //illegal: cannot convert from 'Tuple<ServiceItem, ServiceTranslation>' to 'Tuple<IItem, IItemTranslation>'    }
}

问题:在不更改我的 IItemITranslation 类或其派生类型的情况下,能够传递这些配对的通用列表而不来回转换它们的最简洁方法是什么接口和它们的类型之间?

警告。我试图简化问题,但我实际上并没有使用元组。实际上,我正在使用这样的类:

public class ItemAndTranslationPair<TItem, TItemTranslation> where TItem : class, IItem where TItemTranslation : class, IItemTranslation
{
    TItem Item;
    TTranslation Translation;
}

我的服务返回强类型列表,例如 List&lt;ItemAndTranslationPair&lt;GoodsItem, GoodsTranslation&gt;&gt;,因此当我将项目添加到“通用​​”列表时,它看起来像:

var differentBrandsOfBeans = SomeService.GetCansOfBeans();
//above variable is of type IEnumerable<ItemAndTranslationPair<GoodsItem, GoodsTranslation>>

var items = new List<ItemAndTranslationPair<IItem, IItemTranslation>>();
items.AddRange(differentBrandsOfBeans);

【问题讨论】:

    标签: c# generics covariance contravariance invariance


    【解决方案1】:

    您需要使用接口类型创建元组:

    var canOfBeans = new Tuple<IItem, IItemTranslation>(new GoodsItem(), new GoodsTranslation());
    var massage = new Tuple<IItem, IItemTranslation>(new ServiceItem(), new ServiceTranslation());
    

    当您将它们存储在Tuple&lt;IItem, IItemTranslation&gt; 的列表中时,这对您来说应该是个问题。

    【讨论】:

    • 感谢@Sean 的回复。我在回答中添加了一个警告。但本质上,服务层正在返回强类型配对。我还更新了答案以显示我是如何真正尝试表示这些配对的,它实际上不是元组,而是具有泛型类型参数的类。将这些项目添加到列表时,我试图避免将大小写回界面。
    • @Zac 你无法避免将它们输入为接口,而不是底层类型,因为这是List 所要求的。
    【解决方案2】:

    在泛型类型的类型参数上使用out 修饰符以获得该参数中的协方差

    在当前版本的 C# 中,class 类型不支持此功能,只有 interface 类型(和委托类型)支持,因此您需要编写一个接口(注意使用 out):

    public interface IReadableItemAndTranslationPair<out TItem, out TItemTranslation>
      where TItem : class, IItem
      where TItemTranslation : class, IItemTranslation
    {
      TItem Item { get; }
      TItemTranslation Translation { get; }
    }
    

    请注意,属性不能有 set 访问器,因为这与协方差不兼容。

    使用这种类型,您可以:

    var differentBrandsOfBeans = SomeService.GetCansOfBeans();
    //above variable is of type
    //IEnumerable<IReadableItemAndTranslationPair<GoodsItem, GoodsTranslation>>
    
    var items = new List<IReadableItemAndTranslationPair<IItem, IItemTranslation>>();
    
    items.AddRange(differentBrandsOfBeans);
    

    之所以有效,是因为 IEnumerable&lt;out T&gt; 是协变的,而您的类型 IReadableItemAndTranslationPair&lt;out TItem, out TItemTranslation&gt;TItemTItemTranslation 中都是协变的。

    【讨论】:

    • 非常感谢您的回答。但是我将如何实际实例化这些配对之一?例如在GetCansOfBeans() 方法中,我使用IReadableItemAndTranslationPair 的什么实现?这就是我与其他在线 QA 的协方差和 out 修饰符迷失的地方。
    • @Zac 你应该有一个类,比如`class ItemAndTranslationPair`,它实现了IReadableItemAndTranslationPair&lt;IItem, IItemTranslation&gt; 接口。该类的属性可以同时具有getset(只有get 部分将是接口“合同”的一部分)。 GetCansOfBeans() 方法的返回类型应该只涉及接口,但您“产生”的实际对应该是类的实例。
    • 干杯@Jeppe。在实现中同时拥有getset 部分,而在合同中只有get 是我感到困惑的地方。我可以接受我的GetCansOfBeans() 方法返回接口,尽管我的其他方法(如DeleteCansOfBeans(IEnumerable&lt;ItemAndTranslationPair&lt;GoodsItem, GoodsItemTranslation&gt;&gt;))可能不再适用于这些列表,这似乎并不令人满意。
    猜你喜欢
    • 2013-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 1970-01-01
    • 2021-06-30
    • 1970-01-01
    • 2021-12-06
    相关资源
    最近更新 更多