Preface: I am aware that there are a lot of questions and answers about covariance and contravariance but I'm still feeling muddled and not sure what solution to implement.
I have two interfaces whose implementations are intended to be used together in pairs. One provides the information about a sales item and one provides language dependent information for a sales item.
I do not have control over these interfaces:
public interface IItem
{
decimal Price { get; set; }
}
public interface IItemTranslation
{
string DisplayName { get; set; }
}
I also have two implementations of both these interfaces for a tangible GoodsItem
as well as an intangible ServiceItem
. Again, I do not have control over these interfaces:
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
}
As I said, these are classes that I do not have control over. I want to create a generic list of these pairings (List<Tuple<IItem, IItemTranslation>>
) but I cannot:
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>' }
}
Question: Without changing my IItem
and ITranslation
classes or their derived types, what's the cleanest way to be able to pass around a generic list of these pairings without casting them back and forth between the interface and their type?
Caveat. I was trying to simplify the question but I'm not actually using Tuples. In reality I'm using a class like this:
public class ItemAndTranslationPair<TItem, TItemTranslation> where TItem : class, IItem where TItemTranslation : class, IItemTranslation
{
TItem Item;
TTranslation Translation;
}
and my services are returning strongly typed lists, like List<ItemAndTranslationPair<GoodsItem, GoodsTranslation>>
and therefore when I add items to the 'generic' list it looks like:
var differentBrandsOfBeans = SomeService.GetCansOfBeans();
//above variable is of type IEnumerable<ItemAndTranslationPair<GoodsItem, GoodsTranslation>>
var items = new List<ItemAndTranslationPair<IItem, IItemTranslation>>();
items.AddRange(differentBrandsOfBeans);
Use the
out
modifier on the type parameter of the generic type to obtain covariance in that parameter.In the current version of C#, this is not supported for
class
types, onlyinterface
types (and delegate types), so you would need to write an interface (notice use ofout
):Note that the properties cannot have a
set
accessor since that would be incompatible with the covariance.With this type, you can have:
It works because
IEnumerable<out T>
is covariant, and your typeIReadableItemAndTranslationPair<out TItem, out TItemTranslation>
is covariant in bothTItem
andTItemTranslation
.