Skip to content
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

When registering services, also register Func<T> and Lazy<T> for services #49

Closed
kzu opened this issue Dec 12, 2022 · 0 comments · Fixed by #50
Closed

When registering services, also register Func<T> and Lazy<T> for services #49

kzu opened this issue Dec 12, 2022 · 0 comments · Fixed by #50
Labels
enhancement New feature or request

Comments

@kzu
Copy link
Contributor

kzu commented Dec 12, 2022

The Microsoft.Extensions.DependencyInjection does not provide any implicit
way of exposing a factory-like Func<TService> so that dependencies can be
resolved "lazily" but preserving whatever lifecycle was configured for the service.

This is slightly different than a Lazy<T> since the lazy will (by definition) be
initialized/retrieved only once, irrespective of the lifetime configured for the
service (i.e. if the resolving component is a singleton, it will "promote" the
dependency to a singleton too since it will be resolved only once too).

It would be nice if the attributed services supported both patterns natively.

@kzu kzu added the enhancement New feature or request label Dec 12, 2022
kzu added a commit that referenced this issue Dec 13, 2022
Two patterns are quite typical when doing DI: lazy retrieval and
factory-style retrieval (which might or might not return same
instance, depending on dependency registration). There's a
place for both, so this adds support for both.

The reasoning for making both registrations transient is that
the lifetime of the underlying service is already determined by
the implementation registration itself. Both Lazy and Func just
delegate to the service provider at invocation time. This
simplifies the potential cognitive overhead in understanding
what happens for each.

If callers cache the Lazy<T>, they'd get single-time retrieval,
regardless of the lifetime of the T (that is, the lifetime of the
the lazy-initialized T is now tied to the dependency owner at
that point.

At the same time, the Func<T> will either return a new T on
each invocation or not, depending on how the T was registered. This might complicate things if T is disposable,
since at that point the caller would need to know not to
dispose retrieved instances inadvertently (since the container
itself would do so at the right time). So such a dependency is
generally advised for non-disposable services (or otherwise
transient ones that can be safely disposed after each use via
the Func<T>).

Closes #49
kzu added a commit that referenced this issue Dec 13, 2022
Two patterns are quite typical when doing DI: lazy retrieval and
factory-style retrieval (which might or might not return same
instance, depending on dependency registration). There's a
place for both, so this adds support for both.

The reasoning for making both registrations transient is that
the lifetime of the underlying service is already determined by
the implementation registration itself. Both Lazy and Func just
delegate to the service provider at invocation time. This
simplifies the potential cognitive overhead in understanding
what happens for each.

If callers cache the Lazy<T>, they'd get single-time retrieval,
regardless of the lifetime of the T (that is, the lifetime of the
the lazy-initialized T is now tied to the dependency owner at
that point.

At the same time, the Func<T> will either return a new T on
each invocation or not, depending on how the T was registered. This might complicate things if T is disposable,
since at that point the caller would need to know not to
dispose retrieved instances inadvertently (since the container
itself would do so at the right time). So such a dependency is
generally advised for non-disposable services (or otherwise
transient ones that can be safely disposed after each use via
the Func<T>).

Closes #49
@kzu kzu closed this as completed in #50 Dec 13, 2022
kzu added a commit that referenced this issue Dec 13, 2022
Two patterns are quite typical when doing DI: lazy retrieval and
factory-style retrieval (which might or might not return same
instance, depending on dependency registration). There's a
place for both, so this adds support for both.

The reasoning for making both registrations transient is that
the lifetime of the underlying service is already determined by
the implementation registration itself. Both Lazy and Func just
delegate to the service provider at invocation time. This
simplifies the potential cognitive overhead in understanding
what happens for each.

If callers cache the Lazy<T>, they'd get single-time retrieval,
regardless of the lifetime of the T (that is, the lifetime of the
the lazy-initialized T is now tied to the dependency owner at
that point.

At the same time, the Func<T> will either return a new T on
each invocation or not, depending on how the T was registered. This might complicate things if T is disposable,
since at that point the caller would need to know not to
dispose retrieved instances inadvertently (since the container
itself would do so at the right time). So such a dependency is
generally advised for non-disposable services (or otherwise
transient ones that can be safely disposed after each use via
the Func<T>).

Closes #49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant