New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow List<T> and Collection<T> to be injected into constructor arguments #545

Closed
dotnetjunkie opened this Issue May 5, 2018 · 3 comments

Comments

2 participants
@dotnetjunkie
Collaborator

dotnetjunkie commented May 5, 2018

Simple Injector supports a wide range of collection types, such as:

  • IEnumerable<T>
  • IList<T>
  • ICollection<T>
  • IReadOnlyList<T>
  • IReadOnlyCollection<T>

All these abstractions behave as streams and injected instances are all immutable, which allows them to be injected as singleton.

Besides those abstractions, Simple Injector allows injecting array as mutable type. Because of array's mutability, arrays are injected as transient:

  • T[]

Because Simple Injector allows injecting array as mutable transient collection type, there is no reason why it wouldn't support:

  • List<T>
  • Collection<T>

@dotnetjunkie dotnetjunkie added this to the Backlog milestone May 5, 2018

@dotnetjunkie dotnetjunkie modified the milestones: Backlog, v4.3 May 9, 2018

lobster2012-user added a commit to lobster2012-user/SimpleInjector that referenced this issue Jun 13, 2018

@dotnetjunkie dotnetjunkie modified the milestones: Backlog, v4.4 Jun 15, 2018

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Jun 15, 2018

Even when all elements in an injected array have the Singleton lifestyle, an injected array is always considered to be Transient. Reason for this is its mutability; someone could still change the array, which could break the application, when the array would be reused.

As List<T> is mutable as well, and does not allow its underlying behavior to be influenced (i.e. it doesn't contain any virtual methods), it should be registered as Transient as well. This follows the same argument as for arrays.

The big design question here is: how should Collection<T> behave? There are 3 options here:

  1. Collection<T> is kept mutable and injected as Transient, which mimics the behavior of T[] and List<T>.
  2. Collection<T> is made immutable (i.e. by overriding its virtual methods), but the lifestyle is kept Transient.
  3. Collection<T> is made immutable and its lifestyle gets promoted to the shortest lifestyle of its elements. E.g. in case all the elements are Singletons, the Collection<T> will become a singleton as well, but when one of the elements is a Transient, so will be the Collection<T>.

What is impossible to do however, is to let Collection<T> behave as a stream because, although its command methods can be overridden, its query methods can't. It therefore always caches the items internally.

How should Collection<T> behave?

@TheBigRic

This comment has been minimized.

Collaborator

TheBigRic commented Jun 18, 2018

I would follow the KISS principle here. Option 1: Keep Collection<T> mutable and inject as Transient.

Option 2 is a strange one, because all other immutable implementations are Singleton.

Option 3 sounds ok, but since it is impossible to let Collection<T> behave as a stream, choosing option 3 introduces a third kind of collection and thus will lead to extra documentation and support, while there is no extra feature or use case for this special kind.

Letting Collection<T> behave as an Array and List<T> keeps documentation as simple as possible. I guess this will be the same for the codebase.

@dotnetjunkie dotnetjunkie changed the title from Allow `List<T>` and `Collection<T>` to be injected into constructor arguments to Allow List<T> and Collection<T> to be injected into constructor arguments Oct 10, 2018

@dotnetjunkie

This comment has been minimized.

Collaborator

dotnetjunkie commented Nov 2, 2018

In contrast to what I stated above, a Collection<T> can actually easily function as stream. Although it is impossible to override all methods, inheritance is not required. A Collection<T> is just a wrapper of an underlying IList<T> that is supplied through its constructor. A Collection<T> itself contains no state. When the supplied IList<T> works as a stream, so will the Collection<T> that wraps it.

Since streaming behavior works for Collection<T>, we should go with that behavior. It has the best performance and is the same behavior as what most other collection types have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment