Generic interfaces and inheritance in .NET - c#

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

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

How to use an interface in an interface

I want to create an interface which can handle multiple other object of one interface.
I tried using the interface in the interface and using an object in the new class.
public interface IObject
{
double Value { get; set; }
}
public class FirstObject: IObject
{
double Value { get; set; }
}
public class SecondObject: IObject
{
string Titel { get; set; }
double Value { get; set; }
}
public interface ICollection
{
IObject[] Values { get; set; }
}
public class Collection: ICollection
{
SecondObject[] Values { get; set; }
}
Now I get the error, that my Collection doesn't implement the IObject[] Values member.
I thought when I use an object (SecondObject) which is implementing from the interface IObject the Collection should handle this.
What am I doing wrong and how can I solve this?
You might be off better here using generics:
public interface ICollection<T> where T : IObject
{
T[] Values { get; set; }
}
public class Collection : ICollection<SecondObject>
{
public SecondObject[] Values { get; set; }
}
The reason that it doesn't work now, is that the signature should match exactly. That means the Values should be an array of IObject, which it isn't. Using generics you can solve this, while keeping the type constraint.
A second, but inadvisable solution would be using an explicit interface implementation:
public SecondObject[] Values { get; set; }
IObject[] ICollection.Values
{
get
{
return this.Values;
}
set
{
this.Values = value?.Cast<SecondObject>().ToArray();
}
}

How to properly implement concrete class that inherits interface with List<AnotherInterface> [duplicate]

This question already has answers here:
Understanding Covariant and Contravariant interfaces in C#
(2 answers)
Closed 4 years ago.
I have a proposed dictionary item schema that looks like this:
public interface IDataDictionary
{
List<IDataDictionaryItem> DictionaryItems { get; set; }
}
And the dictionary items as:
public interface IDataDictionaryItem
{
Guid Guild { get; set; }
int SequenceId { get; set; }
string Title { get; set; }
string Value { get; set; }
string Description { get; set; }
string Language { get; set; }
}
When I try to implement this in a concrete class like such:
public class HairColorDictionary : IDataDictionary
{
public List<HairColor> DictionaryItems { get; set; }
}
With the items concrete implementation:
public class HairColor : IDataDictionaryItem
{
public Guid Guild { get; set; }
public int SequenceId { get; set; }
public string Title { get; set; }
public string Value { get; set; }
public string Description { get; set; }
public string Language { get; set; }
}
I get the error message below, and I can't seem to wrap my head around it.
Can someone please enlighten me where this went wrong.
Error message:
Error CS0738 'HairColorDictionary' does not implement interface member 'IDataDictionary.DictionaryItems'. 'HairColorDictionary.DictionaryItems' cannot implement 'IDataDictionary.DictionaryItems' because it does not have the matching return type of 'List'.
I understand what you're trying to get at and the reason it doesn't work is because the interface defined a collection that is non specific (that interface could be implemented), but you're trying to implement that interface member in a specific way.
If you really must do this you can do it with generics. Here's the updated bits:
public interface IDataDictionary<T> where T : IDataDictionaryItem
{
List<T> DictionaryItems { get; set; }
}
public class HairColorDictionary : IDataDictionary<HairColor>
{
public List<HairColor> DictionaryItems { get; set; }
}
T allows you to use anything and the where T:... part puts a constraint on it.
Please realize that is a potential answer to your solution. I've not taken the time to ponder the upsides or downsides of it.
In your implementation, HairColorDictionary is-a IDataDictionary. So i could do something bad like:
IDataDictionary d = new HairColorDictionary();
d.DictionaryItems.add(blah);
Here add method expects something of type IDataDictionaryItem and not necessarily HairColor because d is just IDataDictionary and not HairColorDictionary! So i could do something like
class SkinColor : IDataDictionaryItem { ... }
IDataDictionaryItem s = new SkinColor();
d.DictionaryItems.add(s);
Question is whether this last statement is valid or not? It should be valid because add expects a IDataDictionaryItem which SkinColor correctly implements. But then it should not be valid because the actual public List<HairColor> DictionaryItems only allows HairColors and not SkinColors!!!
To eliminate all this confusion and to maintain strict type safety, you have to follow Covarience and Contravarience rules. Or you can go ahead with #TheMuffinMan's nice answer.

How to get whole types that is used in a class with Roslyn? (all nested/inner types recursively)

Please consider this sample:
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public float Age { get; set; }
public List<Address> Addresses { get; set; }
public IEnumerable<Job> Jobs { get; set; }
public IInterface MyInterface { get; set; }
}
public class Address
{
public string City { get; set; }
public string[] Phones { get; set; }
public MyEnum E1 { get; set; }
}
public class Job
{
public Dictionary<decimal, Address> A1 { get; set; }
public Collection<DateTime> Date { get; set; }
public Tuple<double, BigInteger> A2 { get; set; }
}
public enum MyEnum
{
En1,
En2
}
As you see, I want to get all inner classes/structs/types of Person
so the result is :
Person.GetInnerTypes():
Guid
float
string
IInterface
Address
string[]
MyEnum
Job
List<Address>
IEnumerable<Job>
Dictionary<decimal, Address>
decimal
Collection<DateTime>
DateTime
Tuple<double, BigInteger>
double
BigInteger
The types are collected from everywhere (properties, arguments,...)
Is it possible to find whole types (recursively) by Roslyn?
Does anyone have an idea?
EDIT:
Why do I need this?
The problem comes from creating a code generator, if you see the Bogus library you should define rules for every type at first then create the main rule for Person class so I need to know all types for a class to create a code generator for generating test data! (generate a working class)
EDIT 2:
That's a very broad answer.
If you are interested in the properteis only - just use this question. Find your class' DeclarationSyntax, then find all DescendantNodes of PropertyDeclarationSyntax type. PropertyDeclarationSyntax will give you access to types, and you will go into recursion (don't forget about loops). This should be enough for a prototype.
Even on this step you should be careful with
Nested classes. Types used there will be included into your list too.
Generics. Sometime there are limitations like Type where T : ISomeInterface. Also, nested Generics.
Partial classes
Is Func < T1, T2> the same with Func< T2, T1> for you?
After all, there are questions about your problem itself.
Do you need parents of used types?
Do you care about properties only, or methods\fields\events etc are valuable also?
Should you think about properties inherited from the parents?
If you care about methods - should you think about parameters types?
If you care about methods - should you think about variables defined inside?
Edit1.
Maybe reflection is enough for your task?
static void Main(string[] args)
{
var properties = typeof(DemoPerson).GetProperties();
foreach(var property in properties)
{
Console.WriteLine($"Property: {property.Name}\tType: {property.PropertyType}");
}
Console.ReadLine();
}
public class DemoPerson
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<DemoAddress> Addresses { get; set; }
}
public class DemoAddress
{
public string City { get; set; }
}
With output
Property: Id Type: System.Guid
Property: Name Type: System.String
Property: Addresses Type: System.Collections.Generic.List`1[DemoInnerTypes.Program+DemoAddress]

interface and generics C#

I have a interface like this:
public interface IMyInterface<T> where T:class
{
long OS { get; set; }
T App { get; set; }
}
and another interface like this:
public interface IMyInterfaces
{
List<IMyInterface<T>> Subscribers { get; set; } //this line give me error
}
I got error
You need to specify a concrete type or another generic parameter for T when you use IMyInterface<T>
public interface IMyInterfaces
{
List<IMyInterface<int>> Subscribers { get; set; }
}
OR
public interface IMyInterfaces<TOther>
{
List<IMyInterface<TOther>> Subscribers { get; set; }
}
Note I used TOther to stress that it is another generic parameter, different from the T in IMyInterface but you could use the same name (T) for TOther

Cast concrete type to nested generic base type

Let's say I have nested generic data classes similar to the following:
public class BaseRecordList<TRecord, TUserInfo>
where TRecord : BaseRecord<TUserInfo>
where TUserInfo : BaseUserInfo
{
public virtual IList<TRecord> Records { get; set; }
public virtual int Limit { get; set; }
}
public class BaseRecord<TUserInfo>
where TUserInfo : BaseUserInfo
{
public virtual DateTime CreationTime { get; set; }
public virtual TUserInfo UserInfo { get; set; }
}
public class BaseUserInfo
{
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
With 2 concrete versions like so:
// Project 1: Requires some extra properties
public class Project1RecordList : BaseRecordList<Project1Record, Project1UserInfo> {}
public class Project1Record : BaseRecord<Project1UserInfo>
{
public Guid Version { get; set; }
}
public class Project1UserInfo : BaseUserInfo
{
public string FavouriteFood { get; set; }
}
and
// Project 2: Some properties need alternate names for JSON serialization
public class Project2RecordList : BaseRecordList<Project2Record, Project2UserInfo>
{
[JsonProperty("allRecords")]
public override IList<Project2Record> Records { get; set; }
}
public class Project2Record : BaseRecord<Project2UserInfo> {}
public class Project2UserInfo : BaseUserInfo
{
[JsonProperty("username")]
public override string Name { get; set; }
}
I'm then happy to have 2 repositories that return Project1RecordList and Project2RecordList respectively, but at some point in my code I find myself needing to be able to handle both of these in one place. I figure that at this point I need to be able to treat both of these types as
BaseRecordList<BaseRecord<BaseUserInfo>, BaseUserInfo>
as this is the minimum required to meet the generic constraints, but trying to cast or use "as" throws up errors about not being able to convert.
Is there any way to do this, or even a more sane way to handle this situation without massive amounts of code duplication? If it makes any difference this is for a web app and there are already a large number of data classes, many of which use these nested generics.
What you are talking about is called covariance and MSDN has a great article on this here: https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
First, create a new interface:
interface IBaseRecord<out TUserInfo>
where TUserInfo : BaseUserInfo
{
}
Have BaseRecord inherit from the new interface:
public class BaseRecord<TUserInfo> : IBaseRecord<TUserInfo>
where TUserInfo : BaseUserInfo
{
public virtual DateTime CreationTime { get; set; }
public virtual TUserInfo UserInfo { get; set; }
}
If done right, this should compile:
IBaseRecord<BaseUserInfo> project1 = new Project1Record();
IBaseRecord<BaseUserInfo> project2 = new Project2Record();
To expand this to the BaseRecordList, create IBaseRecordList:
interface IBaseRecordList<out TRecord, out TUserInfo>
where TRecord : IBaseRecord<TUserInfo>
where TUserInfo : BaseUserInfo
{
}
Have BaseRecordList inherit from that:
public class BaseRecordList<TRecord, TUserInfo> : IBaseRecordList<TRecord, TUserInfo>
And then use as such:
IBaseRecordList<IBaseRecord<BaseUserInfo>, BaseUserInfo> project1 = new Project1RecordList();
IBaseRecordList<IBaseRecord<BaseUserInfo>, BaseUserInfo> project2 = new Project2RecordList();
Once you have that setup, just add whatever properties or functions you need to use generically to the interfaces.

Categories