Home > Software design >  Generic interfaces and inheritance in .NET
Generic interfaces and inheritance in .NET

Time:12-18

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

CodePudding user response:

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
  • Related