Skip to content

Fix IResolver/IContainer injected into singletons capturing scoped container instead of root#700

Draft
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-singleton-service-resolution
Draft

Fix IResolver/IContainer injected into singletons capturing scoped container instead of root#700
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-singleton-service-resolution

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 10, 2026

When a singleton is first resolved through a scoped container with WithoutThrowIfDependencyHasShorterReuseLifespan(), any directly injected IResolver/IResolverContext/IContainer dependency captures the scoped container instead of root — causing failures after scope disposal.

var container = new Container(Rules.Default.WithoutThrowIfDependencyHasShorterReuseLifespan());
container.Register<Service>(Reuse.Singleton);

using var scope = container.OpenScope();
var fromScope = scope.Resolve<Service>();

Assert.Same(container, fromScope.Resolver); // was FAILING — got scoped container instead of root

Root cause

ResolverContext.GetRootOrSelfExpr() gates the use of RootOrSelfExpr on ThrowIfDependencyHasShorterReuseLifespan (introduced for #378). When that rule is disabled, it falls back to ResolverContextParamExpr — the current scope's resolver — even for container interfaces directly injected into singletons.

Changes

  • ResolverContext.GetRootOrSelfExprForContainerInterface() — new internal method identical to GetRootOrSelfExpr() but without the ThrowIfDependencyHasShorterReuseLifespan guard. Singletons must always receive the root container for direct interface injection regardless of captive-dependency rules.
  • AddContainerInterfaces() — switched all five container interface wrappers (IResolver, IResolverContext, IContainer, IRegistrator, IServiceProvider) to use the new method.

The ThrowIfDependencyHasShorterReuseLifespan condition is preserved in the original GetRootOrSelfExpr() used by CreateResolutionExpression, so Lazy<ScopedService> inside a singleton still correctly resolves through the scope resolver (#378 behavior unaffected).

Tests added

GHIssue686_Singleton_service_resolved_by_scoped_container_not_root_container covering:

  • IResolver in singleton resolved first via scope with WithoutThrowIfDependencyHasShorterReuseLifespan
  • Same with default rules
  • Resolved from root first, then scope (same singleton, correct resolver)
  • IContainer variant
  • Lazy<ScopedService> in singleton still works (regression for Inconsistent resolution failure #378)

Copilot AI changed the title [WIP] Fix singleton service resolved by scoped container Fix IResolver/IContainer injected into singletons capturing scoped container instead of root Apr 10, 2026
Copilot AI requested a review from dadhi April 10, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Singleton service resolved by scoped container, not root container

2 participants