How can I use Autofac to resolve implementations of an interface only if they aren't "more" specialised?

Viewed 419

Summary

I'd like to know if it's possible to get Autofac to return an enumeration of all registered types that implement an interface on the proviso that they don't also implement an interface that derives from the requested one (i.e. aren't "more specialised" than requested).

Skip to the bottom for a code sample that demonstrates this.

Background

In a program I'm writing, I have a "controller" class that (until recently) accepted collections of 4 different types of collaborators, resolved and injected into it using Autofac during application startup:

public DeviceComponentController(
    IEnumerable<Lazy<IClientRequestProcessor<T>>> clientRequestProcessors,
    IEnumerable<Lazy<IClientResponseGenerator<T>>> clientResponseGenerators,
    IEnumerable<Lazy<IComponentDeviceMessageDecoder<T>>> deviceMessageDecoders,
    IEnumerable<Lazy<IComponentDeviceMessageEncoder<T>>> deviceMessageEncoders)
{
    // ...
}

However, I found that in some cases I needed collaborators to perform some particular logic that didn't really fit into any of these roles (i.e. was not directly related to client request processing, response generation, etc.). I could have "saddled" that logic onto a class already handling one of these things, but that would have violated Single Responsibility Principle.

Instead, I created a new interface IDeviceComponentControllerCollaborator (pretty verbose, I know!), from which these other interfaces extended:

interface IDeviceComponentControllerCollaborator
{
    // ...
}

interface IClientRequestProcessor : IDeviceComponentControllerCollaborator
{
    // ...
}

interface IClientResponseGenerator : IDeviceComponentControllerCollaborator
{
    // ...
}

// Etc.

I modified the controller constructor to accept additional collaborators that didn't fit neatly into any of the existing roles, but were able to interact with the other collaborators:

public DeviceComponentController(
    IEnumerable<Lazy<IClientRequestProcessor<T>>> clientRequestProcessors,
    IEnumerable<Lazy<IClientResponseGenerator<T>>> clientResponseGenerators,
    IEnumerable<Lazy<IComponentDeviceMessageDecoder<T>>> deviceMessageDecoders,
    IEnumerable<Lazy<IComponentDeviceMessageEncoder<T>>> deviceMessageEncoders,
    IEnumerable<Lazy<IDeviceComponentControllerCollaborator<T>>> genericCollaborators)
{
    // ...
}

However, I'm not entirely sure what the best way is of making Autofac resolve the genericCollaborators parameter when I request a DeviceComponentController. What I'd like is it to enumerate every registered IDeviceComponentControllerCollaborator that isn't a client request processor, client response generator, etc.

SSCCE

I've reduced the issue to the code sample below. Any solution for this code sample should hopefully resolve the issue I'm facing as well.

void Main()
{
    var builder = new ContainerBuilder();

    // These types _must_ be registered with all implemented interfaces.
    // In my real program I use an extension method to register these types,
    // which for simplicity uses `.AsImplementedInterfaces()`.
    //
    // However, the extension method in my program returns the registration
    // builder, so I can extend the registration on a case-by-case basis if I want
    // (e.g. adding metadata).
    builder.RegisterType<Foo>().AsImplementedInterfaces();
    builder.RegisterType<Bar>().AsImplementedInterfaces();
    builder.RegisterType<Baz>().AsImplementedInterfaces();
    builder.RegisterType<SomeGenericCollaborator>().AsImplementedInterfaces();
    builder.RegisterType<SomeOtherGenericCollaborator>().AsImplementedInterfaces();
    builder.RegisterType<YetAnotherGenericCallaborator>().AsImplementedInterfaces();

    var container = builder.Build();

    // Should give me Foo.
    var collaboratorsA = container.Resolve<IEnumerable<ISpecialisedCollaboratorA>>();

    // Should give me Bar and Baz.
    var collaboratorsB = container.Resolve<IEnumerable<ISpecialisedCollaboratorB>>();

    // Problem: how do I resolve just the "generic" collaborators?
    // I.e.
    //   - SomeGenericCollaborator
    //   - SomeOtherGenericCollaborator
    //   - YetAnotherGenericCallaborator
    //
    // var genericCollaborators = container.Resolve<...>(); ???
    //
    // This does not work (returns all 6 collaborators):
    // var genericCollaborators = container.Resolve<IEnumerable<ICollaborator>>();
}

// Interfaces
interface ICollaborator
{
}

interface ISpecialisedCollaboratorA : ICollaborator
{
}

interface ISpecialisedCollaboratorB : ICollaborator
{
}

// Implementation
class Foo : ISpecialisedCollaboratorA
{
}

class Bar : ISpecialisedCollaboratorB
{
}

class Baz : ISpecialisedCollaboratorB
{
}

class SomeGenericCollaborator : ICollaborator
{
}

class SomeOtherGenericCollaborator : ICollaborator
{
}

class YetAnotherGenericCallaborator : ICollaborator
{
}

Would this be something I'd need to resolve using named/keyed services, or is there an alternative?

0 Answers
Related