I'm trying to refactor a very specific code to make it more generic, so we can expand the use case, but I'm struggling when dealing with generic types. The code in question is (just adding the signature and the parts I need help):
public class Data { }
public class PersonData : Data { }
public interface IDataSchema<T> where T : Data { }
public class PersonEntityProvider
{
public IDataSchema<PersonData> PersonSchema { get; }
}
public class EntityManager
{
private PersonEntityProvider personEntityProvider;
public PersonEntityProvider PersonEntityProvider => personEntityProvider = new PersonEntityProvider();
}
What exactly I'm trying to do is make the EntityManager
class accept multiple EntityProviders
, via a factory pattern. EntityProviders
requires to have a schema, defined by IDataSchema, that uses an underlying class that represents the data.
So what I tried to do was to make EntityManager
depend on abstraction of EntityProviders
:
public interface IEntityProvider<T> where T : Data
{
IDataSchema<T> DataSchema { get; }
}
public class PersonEntityProvider : IEntityProvider<PersonData>
{
public IDataSchema<PersonData> DataSchema { get; }
}
public class EntityManager
{
private IEntityProvider<Data> entityProvider;
public IEntityProvider<Data> EntityProvider =>
entityProvider = new PersonEntityProvider();
}
But when I try to instantiate the provider, the compiler tells me that I cannot convert PersonEntityProvider
to IEntityProvider<Data>
. I thought it was going to work because PersonData is derivable from Data, but apparently doesn't work. I also thought in make EntityManager generic, but that would require too much changes in pieces I don't wanna touch right now... So I tried to change the interface to make it not be generic:
public interface IEntityProvider
{
IDataSchema<Data> DataSchema { get; }
}
public class PersonEntityProvider : IEntityProvider
{
public IDataSchema<PersonData> DataSchema { get; }
}
public class EntityManager
{
private IEntityProvider entityProvider;
public IEntityProvider EntityProvider => entityProvider = new PersonEntityProvider();
}
Doesn't work as well, this time it tells me that my PersonEntityProvider
does not follow the interface contract because the type of IDataSchema
needs to be Data
, instead of PersonData
.
I feel like I'm missing something, but I'm stuck on this for a few hours. What do you think?
CodePudding user response:
Although, PersonData
inherits from Data
, When they are used Data
isn't PersonalData
they share properties since one is the inheriter.
Here's your problem:
public class EntityManager
{
private IEntityProvider<Data> entityProvider;
public IEntityProvider<Data> EntityProvider =>
entityProvider = new PersonEntityProvider();
}
Here, your entityProvider
and EntityProvider
method are of type IEntityProvider<Data>
not IEntityProvider<PersonData>
... So the issue is with casting.
The solution
public interface IData { }
public class Data : IData { }
public class PersonData : Data { }
public interface IDataSchema<T> where T : IData { }
public interface IEntityProvider<T> where T : IData
{
IDataSchema<T> DataSchema { get; }
}
public class PersonEntityProvider : IEntityProvider<IData>
{
public IDataSchema<IData> DataSchema { get; }
}
public class EntityManager
{
private IEntityProvider<IData> entityProvider;
public IEntityProvider<IData> EntityProvider =>
entityProvider = new PersonEntityProvider();
}
Here, you declare a new interface IData
which Data
implements. Then, PersonData
inherits from Data
. Remember, to ensure that things are generic, keep types as interfaces not classes ... like you did in your question using Data
as the defining type. So, you can use IData
whereever you have PersonData
or Data
classes as return types like you see on my suggested code. I have checked my suggested code on VS and it works.
CodePudding user response:
You could perhaps make the type parameters of your IDataSchema<T> and IEntityProvider<T> interfaces covariant (in essence, the same way IEnumerable<T> is declared):
public interface IDataSchema<out T> where T : Data {}
public interface IEntityProvider<out T> where T : Data
{
IDataSchema<T> DataSchema { get; }
}
(Note the out
keyword annotating the generic type parameter.)
Using covariant type parameters however incurs some restrictions on the interfaces:
- Properties being of a covariant generic type parameter type cannot have a setter declared by the interface.
- Arguments of methods declared by the interface cannot use covariant generic type parameters (the return type of a method can still be a covariant generic type parameter).