diff --git a/docs/core/compatibility/extensions/10.0/getkeyedservice-anykey.md b/docs/core/compatibility/extensions/10.0/getkeyedservice-anykey.md index 16abacdc9d379..e4d63b6ad402d 100644 --- a/docs/core/compatibility/extensions/10.0/getkeyedservice-anykey.md +++ b/docs/core/compatibility/extensions/10.0/getkeyedservice-anykey.md @@ -1,13 +1,16 @@ --- title: "Breaking change: Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey" description: "Learn about the breaking change in .NET 10 where GetKeyedService() and GetKeyedServices() behavior changed when using KeyedService.AnyKey as the lookup key." -ms.date: 11/19/2025 +ms.date: 01/26/2026 ai-usage: ai-assisted --- # Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey -The behavior of the and methods in the `Microsoft.Extensions.DependencyInjection` library was updated to address inconsistencies in handling the 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`. +The behavior of the and methods in the `Microsoft.Extensions.DependencyInjection` library was updated to address inconsistencies in handling the registration. Specifically: + +- `GetKeyedService()` now throws an exception when you attempt to resolve a single service using `KeyedService.AnyKey` as the lookup key. +- `GetKeyedServices()` (plural) no longer returns `AnyKey` registrations when queried with `KeyedService.AnyKey`. ## Version introduced @@ -32,7 +35,7 @@ Additionally, calling `GetKeyedServices()` with `KeyedService.AnyKey` no longer ```csharp var services = serviceProvider.GetKeyedServices(typeof(IMyService), KeyedService.AnyKey); -// Returns an empty collection. +// Returns only services that were registered with a specific key. ``` ## Type of breaking change @@ -41,14 +44,19 @@ This change is a [behavioral change](../../categories.md#behavioral-change). ## Reason for change -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). +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. +- 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). ## Recommended action If you use `GetKeyedService()` or `GetKeyedServices()` with `KeyedService.AnyKey`, review your code and update it to use specific keys instead of `AnyKey`: -- Replace `GetKeyedService(KeyedService.AnyKey)` calls with specific keys or alternative logic to handle service resolution. -- Replace `GetKeyedServices(KeyedService.AnyKey)` calls with specific keys, or update your logic to enumerate only the services you intend to retrieve. +- Update `GetKeyedService(KeyedService.AnyKey)` calls to pass specific keys, or use alternative logic to handle service resolution. +- Update `GetKeyedServices(KeyedService.AnyKey)` calls to pass specific keys, or use alternative logic to enumerate only the services you intend to retrieve. ## Affected APIs diff --git a/docs/core/extensions/dependency-injection/overview.md b/docs/core/extensions/dependency-injection/overview.md index 3c4ce204e1d47..a42bb1ae0dd65 100644 --- a/docs/core/extensions/dependency-injection/overview.md +++ b/docs/core/extensions/dependency-injection/overview.md @@ -1,7 +1,7 @@ --- title: Dependency injection description: Learn how to use dependency injection within your .NET apps. Discover how to define service lifetimes and express dependencies in C#. -ms.date: 10/21/2025 +ms.date: 01/26/2026 ms.topic: overview ai-usage: ai-assisted --- @@ -216,7 +216,11 @@ public class ExampleService ### KeyedService.AnyKey property -The property provides a special key for working with keyed services. You can register a service using `KeyedService.AnyKey` as a fallback that matches any key. This is useful when you want to provide a default implementation for any key that doesn't have an explicit registration. +The property provides a special key for working with keyed services. + +#### Service registration + +You can register a service using `KeyedService.AnyKey` as a fallback that matches any key. This is useful when you want to provide a default implementation for any key that doesn't have an explicit registration. :::code language="csharp" source="snippets/anykey/Program.cs" id="FallbackRegistration"::: @@ -225,8 +229,16 @@ In the preceding example: - Requesting `ICache` with key `"premium"` returns the `PremiumCache` instance. - Requesting `ICache` with any other key (like `"basic"` or `"standard"`) creates a new `DefaultCache` using the `AnyKey` fallback. +#### Query for services + +You can query for all services that were registered *using a specific key* (that is, not with `KeyedService.AnyKey`), by passing `KeyedService.AnyKey` to . + +:::code language="csharp" source="snippets/anykey/Program.cs" id="AnyKeyQuery"::: + +In the preceding example, calling `GetKeyedServices(KeyedService.AnyKey)` returns only the `PremiumCache` instance since it's the only cache that was registered using a specific key in the [service registration](#service-registration) code example. + > [!IMPORTANT] -> Starting in .NET 10, calling `GetKeyedService()` with `KeyedService.AnyKey` throws an because `AnyKey` is intended as a registration fallback, not as a query key. For more information, see [Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey](../../compatibility/extensions/10.0/getkeyedservice-anykey.md). +> Starting in .NET 10, calling `GetKeyedService()` (singular) with `KeyedService.AnyKey` throws an because `AnyKey` shouldn't be used to resolve a single service. For more information, see [Fix issues in GetKeyedService() and GetKeyedServices() with AnyKey](../../compatibility/extensions/10.0/getkeyedservice-anykey.md). ## See also diff --git a/docs/core/extensions/dependency-injection/snippets/anykey/Program.cs b/docs/core/extensions/dependency-injection/snippets/anykey/Program.cs index c29141f0ccdf6..2fbc7103d4008 100644 --- a/docs/core/extensions/dependency-injection/snippets/anykey/Program.cs +++ b/docs/core/extensions/dependency-injection/snippets/anykey/Program.cs @@ -17,17 +17,37 @@ static void FallbackExample() // Register a specific cache for the "premium" key. services.AddKeyedSingleton("premium", new PremiumCache()); - var provider = services.BuildServiceProvider(); + ServiceProvider provider = services.BuildServiceProvider(); // Requesting with "premium" key returns PremiumCache. - var premiumCache = provider.GetKeyedService("premium"); + ICache? premiumCache = provider.GetKeyedService("premium"); Console.WriteLine($"Premium key: {premiumCache}"); // Requesting with any other key uses the AnyKey fallback. - var basicCache = provider.GetKeyedService("basic"); + ICache? basicCache = provider.GetKeyedService("basic"); Console.WriteLine($"Basic key: {basicCache}"); - var standardCache = provider.GetKeyedService("standard"); + ICache? standardCache = provider.GetKeyedService("standard"); Console.WriteLine($"Standard key: {standardCache}"); + + /* This example outputs: + * + * Premium key: Premium cache + * Basic key: basic cache + * Standard key: standard cache + */ // + + // + IEnumerable? keyedCaches = provider.GetKeyedServices(KeyedService.AnyKey); + foreach (ICache cache in keyedCaches) + { + Console.WriteLine($"AnyKey registered cache: {cache}"); + } + + /* This example outputs: + * + * AnyKey registered cache: Premium cache + */ + // }