Mixing open generic and closed types for IEnumerable<T>
Antaris opened this issue · 6 comments
Hi Team,
I'm having trouble getting a particular scenario to work. I want to resolve all instances of a closed generic type, where the registrations may contain both concrete and open registrations:
public interface IHandler<TRequest, TResponse>
{
TResponse Handle(TRequest request);
}
public class OpenHandler<TRequest, TResponse> : IHandler<TRequest, TResponse>
{
public TResponse Handle(TRequest request)
{
return default(TResponse);
}
}
public class ClosedHandler : IHandler<string, int>
{
public int Handle(string request)
{
return int.Parse(request);
}
}
When I try and resolve all instances of IHandler<string, int>
, only the concrete implementation is returned:
var services = new ServiceCollection();
services.AddTransient<IHandler<string, int>, ClosedHandler>();
services.AddTransient(typeof(IHandler<,>), typeof(OpenHandler<,>));
var provider = services.BuildServiceProvider();
var handlers = provider.GetServices<IHandler<string, int>>().ToArray(); // Only 1 item - ClosedHandler
I thought I'd test other containers to see what they do:
class Program
{
static void Main(string[] args)
{
#region Stock
{
var services = new ServiceCollection();
services.AddTransient<IHandler<string, int>, ClosedHandler>();
services.AddTransient(typeof(IHandler<,>), typeof(OpenHandler<,>));
var provider = services.BuildServiceProvider();
var handlers = provider.GetServices<IHandler<string, int>>().ToArray(); // Returns 1
}
#endregion
#region StructureMap
{
var container = new StructureMap.Container(c =>
{
c.For<IHandler<string, int>>().Use<ClosedHandler>();
c.For(typeof(IHandler<,>)).Use(typeof(OpenHandler<,>));
});
var handlers = container.GetAllInstances<IHandler<string, int>>().ToArray(); // Returns 1
}
#endregion
#region Autofac
{
var builder = new Autofac.ContainerBuilder();
builder.RegisterType<ClosedHandler>().As<IHandler<string, int>>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(OpenHandler<,>)).As(typeof(IHandler<,>)).InstancePerLifetimeScope();
var container = builder.Build();
var handlers = container.Resolve<IEnumerable<IHandler<string, int>>>().ToArray(); // Returns 2
}
#endregion
#region Ninject
{
var kernel = new Ninject.StandardKernel();
kernel.Bind<IHandler<string, int>>().To<ClosedHandler>();
kernel.Bind(typeof(IHandler<,>)).To(typeof(OpenHandler<,>));
var handlers = kernel.GetAll<IHandler<string, int>>().ToArray(); // Returns 2
}
#endregion
#region SimpleInjector
{
var container = new SimpleInjector.Container();
container.RegisterCollection(typeof(IHandler<,>), new[]
{
typeof(ClosedHandler),
typeof(OpenHandler<,>)
});
var handlers = container.GetAllInstances<IHandler<string, int>>().ToArray(); // Returns 2
}
#endregion
}
}
Here is my project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="4.4.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Ninject" Version="4.0.0-beta-0134" />
<PackageReference Include="SimpleInjector" Version="4.0.0-beta1" />
<PackageReference Include="StructureMap" Version="4.4.3" />
</ItemGroup>
</Project>
In all but Microsoft.Extensions.DependencyInjection
and StructureMap, all other contains return both instances.
What would your recommendation be to achieve this? Is this functionality missing?
Just curious; for StructureMap (or all of them, for that matter), what happens if you register the closed type after the open type?
Sorry for the late reply - here is the results of changing order:
[0] OpenHandler<>
[1] ClosedHandler
Microsoft.Extensions.DependencyInjection - 1 handler - ClosedHandler
StructureMap - 1 handler - ClosedHandler
Autofac - 2 handlers - OpenHandler<> and then ClosedHandler
Ninject - 2 handlers - ClosedHandler and then OpenHandler<>
SimpleInjector - 2 handlers - OpenHandler<> and then ClosedHandler
@Antaris Does that mean it now returned a different type?
The API might not be the most intuitive, but I think you might be using SM wrong. I think the registration should be something like
var container = new StructureMap.Container(c =>
{
c.For(typeof(IHandler<,>)).Add(typeof(OpenHandler<,>));
c.For(typeof(IHandler<,>)).Use(typeof(ClosedHandler)); // Special case for string and int
});
var handlers = container.GetAllInstances<IHandler<string, int>>().ToArray(); // string and int returns 2
var otherHandlers = container.GetAllInstances<IHandler<string, double>>().ToArray(); // string and double returns 1
Take a look at Use vs Add 😄
This isn't very relevant to the original issue though 😝
@khellang You're right, by making that change, StructureMap behaves like the other containers (with the exception of Ninject which returned them in a different order). Edit: Almost the same - SM seems to always return the open types first, which is the opposite of Ninject, which always seems to return the closed types first. I know there are discussions around preserving order of registered types which is a far reaching issue.
You're also right, this doesn't solve the root issue ;-)