9

This question is a follow up of my previous question: Autofac: Hiding multiple contravariant implementations behind one composite.

I'm trying to find the boundries of what we can do with Autofac's covariance and contravariance support. I noticed that Autofac's ContravariantRegistrationSource only supports generic interfaces with a single generic parameter that is marked with the in keyword. This seems to limit the usefulness of this feature, and I'm wondering if Autofac has other ways in extending the support of covariance and contravariance.

I must admit that I'm not asking this because of a real application design I'm working of. I'm deliberately trying to find Autofac's limits for the sake of education.

So consider the following interface:

public interface IConverter<in TIn, out TOut>
{
    TOut Convert(TIn value);
}

And the following implementation:

public class ObjectToStringConverter : IConverter<object, string>
{
    string IConverter<object, string>.Convert(object value)
    {
        return value.ToString();
    }
}

And the following registation:

var builder = new ContainerBuilder();

builder.RegisterSource(new ContravariantRegistrationSource());

builder.RegisterType<ObjectToStringConverter>()
    .As<IConverter<object, string>>();

var container = builder.Build();

With this design and configuration, I'd expect to be able to do this:

// This call succeeds because IConverter<object, string> is
// explicitly registered.
container.Resolve<IConverter<object, string>>();

// This call fails, although IConverter<string, object> is
// assignable from IConverter<object, string>.
container.Resolve<IConverter<string, object>>();

Or let me put it more abstractly, with the given definitions:

public class A { }
public class B : A { }
public class C : B { }

public class AToCConverter : IConverter<A, C> { ... }

And the following registration:

builder.RegisterType<AToCConverter>()
    .As<IConverter<C, A>>();

I would expect the following calls to succeed:

container.Resolve<IConverter<C, A>>();
container.Resolve<IConverter<B, B>>();
container.Resolve<IConverter<A, C>>();

How can we do this with Autofac?

Community
  • 1
  • 1
Steven
  • 166,672
  • 24
  • 332
  • 435

2 Answers2

4

I think this is a limitation we're unlikely to overcome in Autofac, but it is interesting to explore.

We can do contravariant 'resolve' because given a generic type argument we can find all of the base/interface types to which that argument would be assignable. That is, given string we can search for implementations for object, IComparable etc.

Going in the opposite direction - from an argument type to all of its subclasses - isn't so easy. Given object we'd need some way to look for everything else.

It may be possible to use knowledge of the concrete components registered in the container, e.g. scan all components looking for possible implementations and work backwards, but this isn't great for Autofac because we rely on a 'pull' model to lazily create components in many cases.

Hope this is food for thought, interested to see what you come up with.

Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101
1

You're correct in observing that the ContravariantRegistrationSource only recognizes types with one generic parameter. Looking at the source (currently at approx. line 166) you'll see that limitation right there. Looking at how the registration source is required to provide possible candidates I can understand that lifting the limitation will require more complexity in the implementation.

I would say that this doesn't prove that you have reached Autofac's limits, only the limits of this particular registration source. I'll leave it as an exercise for the reader to enhance the ContravariantRegistrationSource implementation and I'm sure the Autofac project is more than happy to accept it back into the core.

Peter Lillevold
  • 33,668
  • 7
  • 97
  • 131
  • The check for equality with 1 is counting the number of contravariant parameters; an arbitrary number of other (non-contravariant) parameters can still be handled. Cheers! – Nicholas Blumhardt Sep 08 '11 at 04:27