Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Mixing open generic and closed types for IEnumerable<T> #500

Closed
Antaris opened this issue Mar 10, 2017 · 6 comments
Closed

Mixing open generic and closed types for IEnumerable<T> #500

Antaris opened this issue Mar 10, 2017 · 6 comments
Assignees
Milestone

Comments

@Antaris
Copy link

Antaris commented Mar 10, 2017

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?

@khellang
Copy link
Contributor

khellang commented Mar 10, 2017

Just curious; for StructureMap (or all of them, for that matter), what happens if you register the closed type after the open type?

@Antaris
Copy link
Author

Antaris commented Mar 21, 2017

@khellang

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

@khellang
Copy link
Contributor

khellang commented Mar 21, 2017

@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 😝

@Antaris
Copy link
Author

Antaris commented Mar 21, 2017

@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 ;-)

@Eilon
Copy link
Member

Eilon commented Jun 14, 2017

@pakrym - Done/Close? Or more work needed?

@pakrym
Copy link
Contributor

pakrym commented Jun 14, 2017

We reverted previous fix, new PR #533

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants