Ковариация путаницы. Невозможно назначить кортежи реализованных интерфейсов списку кортежей

Предисловие: я знаю, что есть много вопросов и ответов о ковариации и контравариантности, но я все еще чувствую себя не в своей тарелке и не знаю, какое решение реализовать.

У меня есть два интерфейса, реализации которых предназначены для совместного использования в парах. Один предоставляет информацию об элементе продажи, а другой - информацию о языке для элемента продажи.

У меня нет контроля над этими интерфейсами:

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>'    }
}

Вопрос: не меняя мой IItem а также ITranslation классы или их производные типы, каков самый простой способ передать общий список этих пар, не переводя их назад и вперед между интерфейсом и их типом?

Предостережение. Я пытался упростить вопрос, но на самом деле я не использую кортежи. На самом деле я использую такой класс:

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

и мои службы возвращают строго типизированные списки, такие как List<ItemAndTranslationPair<GoodsItem, GoodsTranslation>> и поэтому, когда я добавляю элементы в "общий" список, это выглядит так:

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

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

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 Accessor, так как это было бы несовместимо с ковариацией.

С этим типом вы можете иметь:

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

var items = new List<IReadableItemAndTranslationPair<IItem, IItemTranslation>>();

items.AddRange(differentBrandsOfBeans);

Это работает потому что IEnumerable<out T> является ковариантным, и ваш тип IReadableItemAndTranslationPair<out TItem, out TItemTranslation> ковариантен в обоих TItem а также TItemTranslation,

Вам необходимо создать кортежи с использованием типов интерфейса:

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

Как вы храните их в списке Tuple<IItem, IItemTranslation> это должно быть проблемой для вас.

Другие вопросы по тегам