Skip to content

Commit ccd47f0

Browse files
authored
Document breaking change for GetKeyedService and GetKeyedServices with AnyKey in .NET 10 Preview 3 (#50053)
1 parent 0e45bf5 commit ccd47f0

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

docs/core/compatibility/10.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ If you're migrating an app to .NET 10, the breaking changes listed here might af
7878
| Title | Type of change |
7979
|-------|---------------------|
8080
| [BackgroundService runs all of ExecuteAsync as a Task](extensions/10.0/backgroundservice-executeasync-task.md) | Behavioral change |
81+
| [Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey](extensions/10.0/getkeyedservice-anykey.md) | Behavioral change |
8182
| [Null values preserved in configuration](extensions/10.0/configuration-null-values-preserved.md) | Behavioral change |
8283
| [Message no longer duplicated in Console log output](extensions/10.0/console-json-logging-duplicate-messages.md) | Behavioral change |
8384
| [ProviderAliasAttribute moved to Microsoft.Extensions.Logging.Abstractions assembly](extensions/10.0/provideraliasattribute-moved-assembly.md) | Source incompatible |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
title: "Breaking change: Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey"
3+
description: "Learn about the breaking change in .NET 10 where GetKeyedService() and GetKeyedServices() behavior changed when using KeyedService.AnyKey as the lookup key."
4+
ms.date: 11/19/2025
5+
ai-usage: ai-assisted
6+
---
7+
8+
# Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey
9+
10+
The behavior of the <xref:Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedService(System.IServiceProvider,System.Type,System.Object)> and <xref:Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedServices(System.IServiceProvider,System.Type,System.Object)> methods in the `Microsoft.Extensions.DependencyInjection` library was updated to address inconsistencies in handling the <xref:Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey?displayProperty=nameWithType> registration. Specifically, `GetKeyedService()` now throws an exception when you attempt to resolve a single service using `KeyedService.AnyKey` as the lookup key, and `GetKeyedServices()` (plural) no longer returns `AnyKey` registrations when queried with `KeyedService.AnyKey`.
11+
12+
## Version introduced
13+
14+
.NET 10
15+
16+
## Previous behavior
17+
18+
Previously, calling `GetKeyedService()` with `KeyedService.AnyKey` returned a service registration associated with `AnyKey`. This behavior was inconsistent with the intended semantics, as `AnyKey` is meant to represent a special case of keyed services rather than a specific registration.
19+
20+
Calling `GetKeyedServices()` with `KeyedService.AnyKey` returned all registrations for `AnyKey`. This behavior was also inconsistent with the intended semantics, as `AnyKey` is not meant to enumerate all keyed services.
21+
22+
## New behavior
23+
24+
Starting in .NET 10, calling `GetKeyedService()` with `KeyedService.AnyKey` throws an <xref:System.InvalidOperationException>. This ensures that `AnyKey` can't be used to resolve a single service, as it's intended to represent a special case rather than a specific key.
25+
26+
```csharp
27+
var service = serviceProvider.GetKeyedService(typeof(IMyService), KeyedService.AnyKey);
28+
// Throws InvalidOperationException: "Cannot resolve a single service using AnyKey."
29+
```
30+
31+
Additionally, calling `GetKeyedServices()` with `KeyedService.AnyKey` no longer returns registrations for `AnyKey`. Instead, it adheres to the updated semantics where `AnyKey` is treated as a special case and doesn't enumerate services.
32+
33+
```csharp
34+
var services = serviceProvider.GetKeyedServices(typeof(IMyService), KeyedService.AnyKey);
35+
// Returns an empty collection.
36+
```
37+
38+
## Type of breaking change
39+
40+
This change is a [behavioral change](../../categories.md#behavioral-change).
41+
42+
## Reason for change
43+
44+
The previous behavior of `GetKeyedService()` and `GetKeyedServices()` with `KeyedService.AnyKey` was inconsistent with the intended semantics of `AnyKey`. The changes were introduced to ensure that `AnyKey` is treated as a special case and can't be used to resolve a single service, and to prevent `GetKeyedServices()` from returning `AnyKey` registrations when queried with `AnyKey`. These updates improve the predictability and correctness of the `Microsoft.Extensions.DependencyInjection` library's behavior when working with keyed services. For more details, see the [pull request](https://github.com/dotnet/runtime/pull/113137) and the associated [merge commit](https://github.com/dotnet/runtime/commit/deee462fc8421a7e18b8916eb5a5eacb9d09169d).
45+
46+
## Recommended action
47+
48+
If you use `GetKeyedService()` or `GetKeyedServices()` with `KeyedService.AnyKey`, review your code and update it to use specific keys instead of `AnyKey`.
49+
50+
For `GetKeyedService(KeyedService.AnyKey)`, replace calls to `GetKeyedService()` with `KeyedService.AnyKey` with specific keys or alternative logic to handle service resolution.
51+
52+
For `GetKeyedServices(KeyedService.AnyKey)`, replace calls to `GetKeyedServices()` with `KeyedService.AnyKey` with calls that use specific keys, or update your logic to enumerate only the services you intend to retrieve.
53+
54+
## Affected APIs
55+
56+
- <xref:Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedService(System.IServiceProvider,System.Type,System.Object)?displayProperty=fullName>
57+
- <xref:Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedServices(System.IServiceProvider,System.Type,System.Object)?displayProperty=fullName>

docs/core/compatibility/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ items:
9090
items:
9191
- name: BackgroundService runs all of ExecuteAsync as a Task
9292
href: extensions/10.0/backgroundservice-executeasync-task.md
93+
- name: Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey
94+
href: extensions/10.0/getkeyedservice-anykey.md
9395
- name: Message no longer duplicated in Console log output
9496
href: extensions/10.0/console-json-logging-duplicate-messages.md
9597
- name: Null values preserved in configuration

0 commit comments

Comments
 (0)