0
votes

I want to be able to resolve a collection of services from Autofac that represents all registered types which implement an open generic interface.

public interface IEntityService<in T> where T : Entity
{
    void DoEntityWork(T entity);
}

I have many classes that inherit Entity and many corresponding service classes which implement IEntityService for that Entity.

public class EntityA : Entity { }
public class EntityB : Entity { }
public class EntityC : Entity { }

public class EntityAService : IEntityService<EntityA>
{
    public void DoEntityWork(EntityA entity)
}

public class EntityBService : IEntityService<EntityB>
{
    public void DoEntityWork(EntityB entity)
}

public class EntityCService : IEntityService<EntityC>
{
    public void DoEntityWork(EntityB entity)
}

Here is how I am registering them with Autofac:

builder.RegisterType<EntityAService>().As<IEntityService<EntityA>();
builder.RegisterType<EntityBService>().As<IEntityService<EntityB>();
builder.RegisterType<EntityCService>().As<IEntityService<EntityC>();

What I would like to be able to do is to resolve each one of those IEntityService registrations in a collection. However, attempting to inject them with the following code returns an empty collection:

public class MyProcessingClass(IEnumerable<IEntityService<Entity>> entityServices)
{
    _entityServices = entityServices;
}

I have tried instead registering them all As<IEntityService<Entity>>(), but this throws an ArgumentException with the following message:

The type 'MyProject.Services.EntityAService' is not assignable to service 'MyProject.Interfaces.IEntityService'1[[MyProject.Models.Entity]]

How can I resolve all of the types which implement IEntityService with a type argument that implements Entity?

2
Someone already mentioned contravariance, so I'll just chime in on the Autofac portion. I don't believe Autofac supports resolving all types of an open generic interface (IEntityService<>). You have to resolve a closed type of the interface (IEntityService<EntityC>. You can consider using a non-generic interface, if your use case allows. Then you can resolve a collection of all types implementing the non-generic interface. - jdewerth
@jandrew Guru Stron also mentioned using a non-generic interface. I've been playing with that idea since the suggestion, but have yet to determine how I'll be able to implement that while maintaining my specific type usage. - draconastar

2 Answers

1
votes

Autofac doesn't support this sort of thing out of the box. There is a ContravariantRegistrationSource but it does sort of the inverse of this - if you register IEntityService<Entity> and then resolve IEntityService<EntityA> it'll resolve the IEntityService<Entity> instance for you. It doesn't allow you to resolve IEnumerable<IEntityService<Entity>> and locate all the derived types.

1
votes

This is how contravariance works in C# works, EntityAService is not IEntityService<Entity>, this relation works another way around:

public class EntityService : IEntityService<Entity>
{
    public void DoEntityWork(Entity entity) { }
}

IEntityService<EntityA> x = new EntityService();

One way around would be introducing a non-generic version of IEntityService, registering all your entity services as that one and resolving IEnumerable<IEntityService>.