Home > Blockchain >  Instantiate generic interface that has generic property
Instantiate generic interface that has generic property

Time:05-22

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