Why do partially closed generics work when running in a Function but not when calling ServiceCollection directly?

Viewed 289

I just attempted to use partially closed generic DI registration for the first time using dotnet 3.1, v3 Azure functions and 3.1.8 of MS DependencyInjection package - all the latest at the time of writing.

The types are resolved as hoped when set as constructor dependencies. However, when I call GetService<> on the container directly I get an ArgumentException:

The number of generic arguments provided doesn't equal the arity of the generic type definition

If I saw this error during runtime I'd conclude that partially closed generics aren't supported, but this isn't the case and I can't figure out why.

public interface IMyInterface<TypeA, TypeB>
{

}

public class MyClass<TypeA> : IMyInterface<TypeA, MyConcreteTypeB>
{
}

services.AddSingleton(typeof(IMyInterface<,>), typeof(MyClass<>));

var myObject = services
   .BuildServiceProvider()
   .GetService<IMyInterface<MyConcreteTypeA, MyConcreteTypeB>>();

myObject.Should().Be().OfType<MyClass<MyConcreteTypeA>>();

I can't run the (rough) code above as a unit test but I can resolve IMyInterface<MyConcreteTypeA, MyConcreteTypeB> as a constructor param in an Azure Function.

Update:

Working example: https://github.com/mr-panucci/sandbox

1 Answers

This is an interesting question. The reason it works in Function constructor is because internally Function Host uses DryIoc as DI container implementing a custom service provider of Microsoft.DependencyInjection. DryIoc container in turn supports resolving partially closed generic types. You can explore the Function Host DI wiring here.

Now to solve your problem of unit test to simulate the same behavior of Function host here:

  1. Add nuget DryIoc.Microsoft.DependencyInjection to your test project.
  2. Update the test code like below:
services.AddSingleton(typeof(IMyInterface<,>), typeof(MyClass<>));

var myObject = DryIocAdapter.Create(services).BuildServiceProvider() // using DryIoc.Microsoft.DependencyInjection;
                 .GetService<IMyInterface<MyConcreteTypeA, MyConcreteTypeB>>();

myObject.Should().Be().OfType<MyClass<MyConcreteTypeA>>();
Related