7

I'm using dependency injection pattern for my application without any IoC Container. Now I decided to use some IoC Container because my Composition Root consists of thousands lines of code, but I failed to make it work with my classes, which actively use variance. For example the following interface

public interface IQuery<in TIn, out TOut>
{
    IReadOnlyCollection<TOut> Get(TIn key);
}

and service

public class FakeRepository : IQuery<object, string>
{
    public IReadOnlyCollection<string> Get(object key)
    {
        return new[] { key.ToString() };
    }
}

Pure DI works fine

IQuery<string, object> service = new FakeRepository();

But neither Autofac nor DryIoc can resolve it.

service = autofacContainer.Resolve<IQuery<string, object>>(); // exception
service = dryIocContainer.Resolve<IQuery<string, object>>(); // exception

Do I need some additional setup? Is there any other IoC container that support this? Am I asking too much?

The full code: https://dotnetfiddle.net/vlw17R

Cyril Durand
  • 15,834
  • 5
  • 54
  • 62
Andrey
  • 1,601
  • 2
  • 11
  • 12
  • How do you register your implementation class in autofac or dryloc? The standard dotnet core ServiceCollection has a `.AddSingleton()` method, and that will only resolve the implementation class when exactly `TInterface` is queried. And there is an `.AddSingleton` helper which just calls `AddSingleton()`. – Tamas Hegedus Jan 17 '18 at 13:26
  • 1
    I plan to use convention based registration. `autofacBuilder.RegisterAssemblyTypes(typeof(FakeRepository).Assembly).AsImplementedInterfaces()` `dryIocContainer.RegisterMany(new[] { typeof(FakeRepository).Assembly })` – Andrey Jan 17 '18 at 13:42
  • 4
    The use of covariance and contravariance is typically useful when resolving a collection of services, for instance when publishing events, where handlers for the supertype should handle the published event as well. The need for co/contra variance for non-collection resolves (as you are doing) however is more rare, and especially something that a container can't do out-of-the-box for you, because it doesn't know how to respond in case there are multiple assignable services to resolve. – Steven Jan 17 '18 at 14:30
  • 3
    Can you explain why you need covariance in your particular case (compared to just resolve the actual type) and describe how you wish the container to handle duplicate assignments, or is it guaranteed that there will never be any duplicates/overlap? – Steven Jan 17 '18 at 14:31
  • My current services follows 'return the most specific type, accept the most generic type' principle. For example `Authorizer(IAuthenticator authenticator){...}` and `public class WindowsAuthenticator: IAuthenticator`. To use IoC container I have to change constructor to `Authorizer(IAuthenticator authenticator){...}`. It's not just a lot of work, it makes services more coupled! – Andrey Jan 18 '18 at 08:24

1 Answers1

4

It won't work in current DryIoc versions (stable v2.12.6 and preview v3.0.0-preview-03).

But nothing in theory prevents the registering of open-generic service type with closed or non-generic implementation type.

For the single service-to-implementation registration:

container.Register(typeof(IQuery<,>), typeof(FakeRepository));

DryIoc will throw the exception because of the internal check for implemented types. If I adjust the check to include open-generic version of service type, then this works:

container.Resolve<IQuery<string, object>>();

The similar adjustment may be done to include open-generic service types in RegisterMany.

But the remaining issue will be with ResolveMany. It is rather an implementation detail, but having both closed and open-generic versions of services may produce two instances when resolving a collection.

Concluding, I have created an issue in DryIoc tracker, and will think how to enable this in a safe manner.

Update

The new DryIoc v2.12.7 is released with Register capable of registering the open-generic service type. But not the RegisterMany. Check the issue for more details.

dadhi
  • 4,807
  • 19
  • 25
  • Sounds great! Will I be able to register all my services by one line on code, or will have to manually register every service? – Andrey Jan 18 '18 at 12:17
  • Ideally, by one line. In worse case, `RegisterMany` plus replace the variant registrations with specific `Register` calls. – dadhi Jan 18 '18 at 13:43
  • Updated my answer. – dadhi Jan 19 '18 at 08:20
  • I'm impressed! Thanks for that. Here's updated convention based registration using this feature: https://dotnetfiddle.net/BDgdwu – Andrey Jan 19 '18 at 09:16