Skip to content

Added EmptyServiceProvider#129578

Draft
svick wants to merge 5 commits into
dotnet:mainfrom
svick:emptyserviceprovider
Draft

Added EmptyServiceProvider#129578
svick wants to merge 5 commits into
dotnet:mainfrom
svick:emptyserviceprovider

Conversation

@svick

@svick svick commented Jun 18, 2026

Copy link
Copy Markdown
Member

Closes #120198.

Adds the API-approved EmptyServiceProvider, a singleton IServiceProvider that contains no application services, so consumers no longer need to define their own internal empty-provider implementations.

API

namespace Microsoft.Extensions.DependencyInjection;

public sealed class EmptyServiceProvider : IKeyedServiceProvider, IServiceProviderIsKeyedService
{
    public static EmptyServiceProvider Instance { get; }

    object? IKeyedServiceProvider.GetKeyedService(Type serviceType, object? serviceKey);
    object IKeyedServiceProvider.GetRequiredKeyedService(Type serviceType, object? serviceKey);
    object? IServiceProvider.GetService(Type serviceType);
    bool IServiceProviderIsKeyedService.IsKeyedService(Type serviceType, object? serviceKey);
    bool IServiceProviderIsService.IsService(Type serviceType);
}

It lives in Microsoft.Extensions.DependencyInjection.Abstractions.

Behavior

The provider is not literally empty: it mirrors the behavior of new ServiceCollection().BuildServiceProvider() as closely as possible (ignoring object identity and disposal tracking), so it is a drop-in stand-in. Specifically:

  • Resolves the built-in services IServiceProvider, IServiceScopeFactory, IServiceProviderIsService and IServiceProviderIsKeyedService.
  • IServiceScopeFactory.CreateScope() returns a scope whose ServiceProvider is the empty provider again.
  • Resolving IEnumerable<T> yields an empty sequence (for both keyed and non-keyed requests).
  • GetRequiredKeyedService throws the same InvalidOperationException as the real provider for unregistered services.
  • KeyedService.AnyKey is handled the same way as the real provider (including returning null for the open generic IEnumerable<>).
  • Requesting an IEnumerable<SomeValueType> under NativeAOT throws the same InvalidOperationException as the real provider, since the array type may not have been generated.

Scope support is implemented via a private nested type rather than being exposed on the public surface, keeping it an implementation detail.

Tests

Added EmptyServiceProviderTests. The core of the suite are theories that compare EmptyServiceProvider's outcome against a real new ServiceCollection().BuildServiceProvider() across a matrix of service types and keys (including AnyKey, open generics and value-type enumerables), reducing each call to a comparable outcome token. The tests pass both on the regular runtime and when compiled with NativeAOT (-p:TestNativeAot=true).

Drive-by

Renamed ServiceProvider.VerifyAotCompatibility to IsDynamicCodeSupported (inverting the value) so it follows boolean property naming conventions and matches RuntimeFeature.IsDynamicCodeSupported, which it wraps. The same helper is used by EmptyServiceProvider.

Note

This pull request was authored with the assistance of GitHub Copilot.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new public singleton EmptyServiceProvider to Microsoft.Extensions.DependencyInjection.Abstractions to provide a reusable “empty” IServiceProvider implementation that still exposes DI built-in services and matches new ServiceCollection().BuildServiceProvider() behavior. As part of enabling this, the PR also renames the DI implementation’s internal AOT/dynamic-code capability flag to IsDynamicCodeSupported and updates related call-site code and suppressions, plus adds focused tests.

Changes:

  • Introduces EmptyServiceProvider.Instance (public API) in the Abstractions assembly, including keyed-service behavior and scope factory support.
  • Renames internal DI implementation switch from VerifyAotCompatibility to IsDynamicCodeSupported and updates AOT-related assertions/suppressions accordingly.
  • Adds EmptyServiceProviderTests to validate behavior equivalence with a real ServiceProvider.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/EmptyServiceProvider.cs Adds the EmptyServiceProvider singleton implementation (new public API).
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs Publishes the new public API surface in the reference assembly.
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx Adds resource strings needed by EmptyServiceProvider exceptions.
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/EmptyServiceProviderTests.cs Adds tests comparing EmptyServiceProvider behavior to a real built provider.
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs Renames internal capability flag to IsDynamicCodeSupported.
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs Updates assertions/suppressions to use the renamed dynamic-code flag.
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs Updates suppressions/assertions to use IsDynamicCodeSupported.
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs Updates suppressions/assertions to use IsDynamicCodeSupported.
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs Updates AOT gating logic to use !IsDynamicCodeSupported semantics.

Copilot AI review requested due to automatic review settings June 18, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: EmptyServiceProvider

2 participants