2017-01-06 1 views
0

Préface: Je suis conscient qu'il y a beaucoup de questions et de réponses sur la covariance et la contravariance, mais je me sens encore confus et je ne sais pas quelle solution mettre en œuvre.Confusion de covariance. Impossible d'affecter des tuples d'interfaces implémentées à la liste des tuples

J'ai deux interfaces dont les implémentations sont destinées à être utilisées ensemble par paires. L'un fournit les informations sur un élément de vente et l'autre fournit des informations dépendantes de la langue pour un élément de vente.

Je n'ai pas de contrôle sur ces interfaces:

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

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

J'ai aussi deux implémentations de ces deux interfaces pour une GoodsItem tangible ainsi qu'une ServiceItem intangible. Encore une fois, Je n'ai pas de contrôle sur ces 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 
} 

Comme je l'ai dit, ce sont les classes que je n'ai pas le contrôle. Je veux créer une liste générique de ces appariements (List<Tuple<IItem, IItemTranslation>>) mais je ne peux pas:

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: Sans changer mes IItem et ITranslation classes ou leurs types dérivés, quelle est la manière la plus propre pour être en mesure de passer autour une liste générique de ces appariements sans les retourner entre l'interface et leur type?

Avertissement. J'essayais de simplifier la question mais je n'utilise pas vraiment Tuples. En réalité, j'utilise une classe comme ceci:

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

et mes services reviennent listes fortement typé, comme List<ItemAndTranslationPair<GoodsItem, GoodsTranslation>> et donc quand j'ajouter des articles à la liste « générique », il ressemble à:

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

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

Répondre

1

Utilisez le modificateur out sur le paramètre type du type générique pour obtenir la covariance dans ce paramètre.

Dans la version actuelle de C#, ce n'est pas pris en charge pour class types, seuls interface types (et types de délégués), de sorte que vous devez écrire une interface (utilisation de l'avis de out):

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

Notez que les propriétés ne peuvent pas avoir d'accesseur set car cela serait incompatible avec la covariance.Avec ce type

, vous pouvez:

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

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

items.AddRange(differentBrandsOfBeans); 

Cela fonctionne parce que IEnumerable<out T> est covariant, et votre type IReadableItemAndTranslationPair<out TItem, out TItemTranslation> est covariante dans les deux TItem et TItemTranslation.

+0

Merci beaucoup pour votre réponse. Mais comment pourrais-je réellement instancier un de ces appariements? par exemple. dans la méthode 'GetCansOfBeans()' quelle implémentation de 'IReadableItemAndTranslationPair' est-ce que j'utilise? C'est là que je me perds avec d'autres QA en ligne pour la covariance et le modificateur 'out'. – Zac

+0

@Zac Vous devriez avoir une classe telle que 'class ItemAndTranslationPair ' qui implémente l'interface 'IReadableItemAndTranslationPair '. La classe pourrait avoir à la fois 'get' et' set' pour ses propriétés (seule la partie 'get' ferait partie de l'interface" contract "). Le type de retour de la méthode 'GetCansOfBeans()' devrait impliquer seulement l'interface, mais les paires réelles que vous "cédez" devraient être des instances de la classe. –

+0

Cheers @Jeppe. Avoir à la fois la partie 'get' et' set' sur la mise en œuvre tout en ayant seulement 'get' sur le contrat était l'endroit où je devenais confus. Je peux vivre avec ma méthode 'GetCansOfBeans()' retournant l'interface, bien que mes autres méthodes comme 'DeleteCansOfBeans (IEnumerable >)' ne fonctionneront probablement plus avec ces listes, ce qui semble insatisfaisant. – Zac

1

vous devez créer les tuples en utilisant les types d'interface:

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

Comme vous les stocker dans une liste de Tuple<IItem, IItemTranslation> cela devrait être un problème pour vous.

+0

Merci pour la réponse @Sean. J'ai ajouté une mise en garde à ma réponse. Mais essentiellement, la couche de service renvoie les paires fortement typées. J'ai également mis à jour la réponse pour montrer comment j'essaie vraiment de représenter ces appariements, ce n'est pas réellement un Tuple mais une classe avec des paramètres de type génériques. J'essaie d'éviter de revenir à l'interface lors de l'ajout de ces éléments à la liste. – Zac

+1

@Zac Vous ne pouvez pas éviter de les avoir tapés comme l'interface, plutôt que le type sous-jacent, parce que c'est ce que le 'List' nécessite. – Servy