0

I have the following scenario that involves a couple of interfaces as below

    internal interface ITranslation
    {
        string LanguageCode { get; set; }
        string Title { get; set; }
    }

Any object that hold translations will implement the ITranslation interface. Some of these objects can have synonyms as well, so I have another interface

    internal interface ITranslationWithSynonmys : ITranslation
    {
        IList<string> Synonyms { get; set; }
    }

Next step I have defined ITranslatable<T> interface for any object that has translations and can be translated in different languages

    internal interface ITranslatable<T> where T : ITranslation
    {
        IList<T> Translations { get; set; }
    }

while when there are synonyms involved the ITranslatableWithSynonyms<T> looks like this

    internal interface ITranslatableWithSynonyms<T> : ITranslatable<T> where T : ITranslationWithSynonmys
    {
        IList<T> SynonymTanslations { get; set; }
    }

Concrete implementations of ITranslation and ITranslationWithSynonmys would be

    internal class BaseTranslation : ITranslation
    {
        public string Title { get; set; }
        public string LanguageCode { get; set; }
    }

    internal class BaseTranslationWithSynonmys : ITranslationWithSynonmys
    {
        public IList<string> Synonyms { get; set; }
        public string LanguageCode { get; set; }
        public string Title { get; set; }
    }

while an entity that can be translated would be

    internal class TranslatableEntity : ITranslatable<ITranslation>
    {
        public IList<ITranslation> Translations { get; set; }
    }

and if it has synomys

    internal class TranslatableWithSynonymsEntity : ITranslatableWithSynonyms<ITranslationWithSynonmys>
    {
        public IList<ITranslationWithSynonmys> SynonymTanslations { get; set; }
        public IList<ITranslationWithSynonmys> Translations { get; set; }
    }

Next, I'm creating a service that can translate any object that implements ITranslatable<T> and I have defined it as

    internal class TranslationService
    {
        internal string Translate(ITranslatable<ITranslation> translatable, string languageCode)
        {
            // It will iterate through the Translations list to find the correct translation
            return string.Empty;
        }
    }

Now, when I try to use the service, I'm writting

var translationService = new TranslationService();
var translatableEntity = new TranslatableEntity();
var translatableWithSynonymsEntity = new TranslatableWithSynonymsEntity();
string x = translationService.Translate(translatableEntity, "en");
string y = translationService.Translate(translatableWithSynonymsEntity, "en");

and here the last line translationService.Translate(translatableWithSynonymsEntity, "en") fails to compile with error CS1503: Argument 1: cannot convert from 'TestInheritance.TranslatableWithSynonymsEntity' to 'TestInheritance.ITranslatable<TestInheritance.ITranslation>'

It's true that TranslatableWithSynonymsEntity doesn't implement ITranslatable<ITranslation>, but it implements ITranslatableWithSynonyms<ITranslationWithSynonmys> with both ITranslatableWithSynonyms<T> inheriting from ITranslatable<T> and ITranslationWithSynonmys inheriting from ITranslation.

I can get the code to compile by having TranslatableWithSynonymsEntity implement both ITranslatableWithSynonyms<ITranslationWithSynonmys> and ITranslatable<ITranslation>, but that means managing two lists and it doesn't look clean.

    internal class TranslatableWithSynonymsEntity : ITranslatableWithSynonyms<ITranslationWithSynonmys>, ITranslatable<ITranslation>
    {
        public IList<ITranslationWithSynonmys> SynonymTanslations { get; set; }
        public IList<ITranslationWithSynonmys> Translations { get; set; }
        IList<ITranslation> ITranslatable<ITranslation>.Translations { get; set; }
    }

Is there a way to avoid this? Or am I taking a wrong approach?

Thank you

Albert
  • 1,015
  • 2
  • 10
  • 28
  • 1
    It looks like you can drop generics altogether - it's not clear what you need them for here? – Oliver Dec 12 '22 at 10:31
  • I need generics because ITranslatable can have a list of ITranslation or any interface inheriting from ITranslation. I don't want it to return always ITranslatable. As you can see in the case of TranslatableWithSynonymsEntity it returns IList – Albert Dec 12 '22 at 11:12
  • Now, due to your ITranslatableWithSynonyms and ITranslatable interfaces specifying some `IList Something {get;set;}` properties, it's not possible to make them covariant. So, you either have to find a way to avoid such properties with setters and some non-covariant interface type as the property's return type which do not allow making the interface co-variant, or you have to find a different class/interface design altogether... –  Dec 12 '22 at 11:23
  • @MySkullCaveIsADarkPlace Based on https://stackoverflow.com/questions/5832094/covariance-and-ilist one option would be to use IReadOnlyList in place of IList. Probably I can adapt that – Albert Dec 12 '22 at 11:27

1 Answers1

1

Generic parameters are invariant by default, in the method Translate you want the type to be <ITranslation>, so you must provide a type whose (or its parents') generic parameter is exactly <ITranslation>.

In your example you cannot simply mark the parameter as covariant because it contains a property has both getter and setter.

Since the problem is the generic parameter, to solve the problem, don't specify one, in fact you have already constrained the generic parameter.

interface ITranslatable<T> where T : ITranslation

The method (or the class) just need to be declared with the same constraint.

internal string Translate<T>(ITranslatable<T> translatable, string languageCode)
     where T : ITranslation
shingo
  • 18,436
  • 5
  • 23
  • 42
  • In that case Translations property of TranslatableWithSynonymsEntity will return IList and I'll have to cast it any place I want to access the elements of the list as ITranslationWithSynonmys. That's what I'm trying to avoid – Albert Dec 12 '22 at 15:04
  • actually you're right. Changing the signature to ``` internal string Translate(ITranslatable translatable, string languageCode) where T : ITranslation ``` works – Albert Dec 12 '22 at 16:22