simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 155 forks source link

Container resolves IEnumerable<Something<TSomethingElse>> when registering only Something<TSomething> #922

Closed andre-ss6 closed 2 years ago

andre-ss6 commented 3 years ago

Describe the bug

If I call container.Collection.Register<IFoo<IBar>>(new[] {typeof(FooBar)}), I'm able to resolve IEnumerable<IFoo<IBaz>>.

Note the difference between the two calls: IFoo<IBar> vs IFoo<IBaz>

Expected behavior

Trying to resolve IEnumerable<IFoo<IBaz>> should throw a ActivationException telling me that this type hasn't been registered.

Actual behavior

An empty enumerable of IFoo<IBaz> is returned by SimpleInjector, just like the 3.x behavior.

To Reproduce

interface IFoo<T> { }
class FooBar : IFoo<Bar> { }
class Bar { }
class Baz { }

class Program
{
    static void Main(string[] args)
    {
        var container = new Container();
        container.Collection.Register<IFoo<Bar>>(new[] { typeof(FooBar) });
        var bug = container.GetInstance<IEnumerable<IFoo<Baz>>>();
    }
}

Additional context

Add any other context about the problem here. e.g.:

dotnetjunkie commented 3 years ago

This is not a bug, but rather by design. Simple Injector considers the collection of IFoo<Baz> to be available because there is a collection for IFoo<T>.

Simple Injector forces you to explicitly register a collection because it would otherwise give a false sense of security, because Simple Injector would otherwise resolve an incomplete object graph (a graph without the collection).

In this case, however, Simple Injector assumes that since you registered your collection with IFoo<T> elements, you registered them all. That's because typically you would register your implementations of a generic interface all at once using auto-registration, but also because under the covers Simple Injector can't make the distinction, because internally it's the same.

This behavior is by design and changing it would be a severe breaking change for a lot of users.

You likely stumbled upon this issue because you forgot to register your IFoo<Baz>, but have you considered using the API that allows auto-regisrering all IFoo<T> implementations at once?

andre-ss6 commented 3 years ago

@dotnetjunkie thanks.

I stumbled upon the issue because my team recently upgraded from Simple Injector 3.3.2 to 5.3.2. From 3.x to 5.x there was a breaking change that collections of types were not resolved automagically anymore, instead having to explicitly register them. However, we were having inconsistent issues with these IEnumerable resolutions, one time they would work and the other not, even though in both instances we expected them to fail because there were no explicit registrations.

I feared this would be by design, but wanted to make sure. I think this inconsistency is quite confusing.