Added EmptyServiceProvider#129578
Conversation
|
Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection |
There was a problem hiding this comment.
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
VerifyAotCompatibilitytoIsDynamicCodeSupportedand updates AOT-related assertions/suppressions accordingly. - Adds
EmptyServiceProviderTeststo validate behavior equivalence with a realServiceProvider.
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. |
Closes #120198.
Adds the API-approved
EmptyServiceProvider, a singletonIServiceProviderthat contains no application services, so consumers no longer need to define their own internal empty-provider implementations.API
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:IServiceProvider,IServiceScopeFactory,IServiceProviderIsServiceandIServiceProviderIsKeyedService.IServiceScopeFactory.CreateScope()returns a scope whoseServiceProvideris the empty provider again.IEnumerable<T>yields an empty sequence (for both keyed and non-keyed requests).GetRequiredKeyedServicethrows the sameInvalidOperationExceptionas the real provider for unregistered services.KeyedService.AnyKeyis handled the same way as the real provider (including returningnullfor the open genericIEnumerable<>).IEnumerable<SomeValueType>under NativeAOT throws the sameInvalidOperationExceptionas 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 compareEmptyServiceProvider's outcome against a realnew ServiceCollection().BuildServiceProvider()across a matrix of service types and keys (includingAnyKey, 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.VerifyAotCompatibilitytoIsDynamicCodeSupported(inverting the value) so it follows boolean property naming conventions and matchesRuntimeFeature.IsDynamicCodeSupported, which it wraps. The same helper is used byEmptyServiceProvider.Note
This pull request was authored with the assistance of GitHub Copilot.