Skip to content

ServiceProvider.GetKeyedServices<T>() creates new instances of Singleton services instead of returning existing ones when you request them with KeyedService.AnyKey #113472

Closed
@denisbredikhin

Description

@denisbredikhin

Description

I encountered an issue with IServiceProvider.GetKeyedServices() in .NET 8 and .NET 9 where it creates new instances of Singleton services instead of returning existing ones when you mix using of KeyedService.AnyKey and of specific keys.

Reproduction Steps

  1. Create a new .NET project.
  2. Add the following dependencies:
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
     <PackageReference Include="MSTest" Version="3.8.2" />
  3. Add the following test code:
    [TestClass]
    public sealed class Test1
    {
        [TestMethod]
        public void TestMethod1()
        {
            var services = new ServiceCollection();
            services.AddKeyedSingleton<TestService>("key1");
            services.AddKeyedSingleton<TestService>("key2");
            services.AddKeyedSingleton<TestService>("key3");
    
            var serviceProvider = services.BuildServiceProvider();
    
            var allInstances = serviceProvider.GetKeyedServices<TestService>(KeyedService.AnyKey).ToArray();
            var service1 = serviceProvider.GetKeyedService<TestService>("key1");
            var service2 = serviceProvider.GetKeyedService<TestService>("key2");
            var service3 = serviceProvider.GetKeyedService<TestService>("key3");
    
            Assert.AreEqual(3, allInstances.Length);
            Assert.Contains(service1, allInstances);
            Assert.Contains(service2, allInstances);
            Assert.Contains(service3, allInstances);
        }
    }
    
    public class TestService
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();
    
        public override bool Equals(object obj)
        {
            if (obj is TestService other)
            {
                return Id == other.Id;
            }
            return false;
        }
    
        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }
    }

Expected behavior

The test should pass, and GetKeyedService<TestService>("key1") should return the same instance that was created when calling GetKeyedServices<TestService>(KeyedService.AnyKey).

Actual behavior

The test fails because GetKeyedService<TestService>("key1") creates a new instance instead of returning the existing one. This issue persists regardless of the order in which the services are requested: when calling GetKeyedServices<TestService>("key1") first, it also creates new instances when calling GetKeyedServices<TestService>(KeyedService.AnyKey) instead of returning the already created ones.

Regression?

No response

Known Workarounds

It works if you register your singletons in the following way:

services.AddKeyedSingleton("key1", (sp, key) => key== KeyedService.AnyKey ? sp.GetKeyedService<TestService>("key1") : new TestService() );
services.AddKeyedSingleton("key2", (sp, key) => key == KeyedService.AnyKey ? sp.GetKeyedService<TestService>("key2") : new TestService());
services.AddKeyedSingleton("key3", (sp, key) => key == KeyedService.AnyKey ? sp.GetKeyedService<TestService>("key3") : new TestService());

However, I'm not sure if this is correct workaround and if won't cause any side effects.

Configuration

.NET 8 and .NET 9
Microsoft.Extensions.Hosting Version: 9.0.3
Microsoft.Extensions.DependencyInjection Version: 9.0.3

Other information

A similar issue was previously fixed for services registered with specific instances using IServiceCollection.AddKeyedSingleton(object? serviceKey, TService implementationInstance) in #95582. However, the problem with services created by the DI container itself remains

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions