简体   繁体   中英

How can I store different objects inheriting from same interface, with generic types, in a single variable in this example?

Let's say I have a data model consisting of 2 classes, both implementing the same interface:

    interface IEntity { }
    class EntityTypeA : IEntity { }
    class EntityTypeB : IEntity { }

I have a generic service which contains a list of these entities and does something with them. There are multiple different implementations of the Service, all inheriting from IService, but let's say for now there's just one, "Service".

    interface IService<T> where T : class, IEntity {
        // stuff
        T GetEntity(Func<T, bool> linq);
    }

    class Service<T> : IService<T> where T : class, IEntity {
        // stuff
        IEnumerable<T> _entities;
    }

At this point I can easily create new services for various entities and work with them. Add new entities of a specific type to them, call methods, get them back without having to cast anything manually.

    IService<EntityTypeA> serviceA = new Service<EntityTypeA>();
    IService<EntityTypeB> serviceB = new Service<EntityTypeB>();

All fine, but now I want to have all these services stored in one place so I can easily fetch the one I want later, without having to keep them all in a separate variable.

Ultimately I wanted to be able to do sth like this:

    _manager = new ServiceManager();
    _manager.AddService("A", serviceA);
    _manager.AddService("B", serviceB);
    IService<EntityTypeA> serviceA = _manager.GetService<EntityTypeA>("A");

So I tried something like this:

class ServiceManager {
        IDictionary<string, IService<IEntity>> _services;

        public void AddService<T>(string key, IService<T> manager) where T : class, IEntity {
            _services[key] = (IService<IEntity>)manager;
        }

        public IService<T> GetService<T>(string key) where T : class, IEntity {
            return (IService<T>)_services[key];
        }
    }

The issue here is the "invalid cast exception" when calling the AddService (and probably GetService as well) methods, I cannot cast and store an Service<EntityTypeA> into IService<IEntity> . Which was a bit of a surprise to me, since EntityTypeA implements IEntity and Service implements IService...

So my question is: How can I store all these generic services in a single variable so I can easily get them with one method from the manager? I'd like this manager to be a single instance responsible for managing all these services but I have no idea how to hold all these generic classes in it.

You can't store an Service<EntityTypeA> into IService<IEntity> because IService is invariant on T . Generic type parameters are by default invariant, unless you have declared them otherwise. Foo<Derived> is not assignable to Foo<Base> in general . See this post for why.

Depending on what the // stuff is in IService , you can potentially make T covariant , allowing you to assign a value of type IService<EntityTypeA> (and hence Service<EntityTypeA> ) to a variable of type IService<IEntity> .

You do this by adding an out modifier on T :

interface IService<out T> where T : class, IEntity {
    // stuff
    T GetEntity(Func<T, bool> linq);
}

This will not work if IService has methods that take in a T (among other things):

interface IService<out T> where T : class, IEntity {
    // stuff
    T GetEntity(Func<T, bool> linq);
    void Foo(T t); // compiler error
}

as this will break type safety:

IService<IEntity> s = new Service<EntityTypeA>();
s.Foo(new EntityTypeB()); // breaks type safety! I can now give Service<EntityTypeA> a EntityTypeB object!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM