Ковариация путаницы. Невозможно назначить кортежи реализованных интерфейсов списку кортежей
Предисловие: я знаю, что есть много вопросов и ответов о ковариации и контравариантности, но я все еще чувствую себя не в своей тарелке и не знаю, какое решение реализовать.
У меня есть два интерфейса, реализации которых предназначены для совместного использования в парах. Один предоставляет информацию об элементе продажи, а другой - информацию о языке для элемента продажи.
У меня нет контроля над этими интерфейсами:
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>
это должно быть проблемой для вас.