Add Keyed Services Support to Dependency Injection (#87183)
authorBenjamin Petit <benjaminpetit@users.noreply.github.com>
Thu, 13 Jul 2023 15:58:48 +0000 (17:58 +0200)
committerGitHub <noreply@github.com>
Thu, 13 Jul 2023 15:58:48 +0000 (17:58 +0200)
35 files changed:
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.Keyed.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.cs
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/FromKeyedServicesAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IKeyedServiceProvider.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IServiceProviderIsKeyedService.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Resources/Strings.resx
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceCollectionServiceExtensions.Keyed.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceCollectionServiceExtensions.cs
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceDescriptor.cs
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceKeyAttribute.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderKeyedServiceExtensions.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ServiceProviderIsKeyedServiceSpecificationTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/DependencyInjectionEventSource.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/Resources/Strings.resx
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteChain.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteValidator.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/FactoryCallSite.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ResultCache.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCacheKey.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceCallSite.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceDescriptorExtensions.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/CallSiteTests.cs
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/KeyedServiceProviderContainerTests.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceCollectionKeyedServiceExtensionsTest.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs
src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderEngineScopeTests.cs

index abb9b58..c9b3d24 100644 (file)
@@ -29,6 +29,12 @@ namespace Microsoft.Extensions.DependencyInjection
         public void Dispose() { }
         public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
     }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Parameter)]
+    public partial class FromKeyedServicesAttribute : System.Attribute
+    {
+        public FromKeyedServicesAttribute(object key) { }
+        public object Key { get { throw null; } }
+    }
     public partial interface IServiceCollection : System.Collections.Generic.ICollection<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IEnumerable<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IList<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.IEnumerable
     {
     }
@@ -41,6 +47,10 @@ namespace Microsoft.Extensions.DependencyInjection
     {
         bool IsService(System.Type serviceType);
     }
+    public partial interface IServiceProviderIsKeyedService : IServiceProviderIsService
+    {
+        bool IsKeyedService(System.Type serviceType, object? serviceKey);
+    }
     public partial interface IServiceScope : System.IDisposable
     {
         System.IServiceProvider ServiceProvider { get; }
@@ -49,10 +59,19 @@ namespace Microsoft.Extensions.DependencyInjection
     {
         Microsoft.Extensions.DependencyInjection.IServiceScope CreateScope();
     }
+    public partial interface IKeyedServiceProvider : System.IServiceProvider
+    {
+        object? GetKeyedService(System.Type serviceType, object? serviceKey);
+        object GetRequiredKeyedService(System.Type serviceType, object? serviceKey);
+    }
     public partial interface ISupportRequiredService
     {
         object GetRequiredService(System.Type serviceType);
     }
+    public static partial class KeyedService
+    {
+        public static object AnyKey { get { throw null; } }
+    }
     public delegate object ObjectFactory(System.IServiceProvider serviceProvider, object?[]? arguments);
     public delegate T ObjectFactory<T>(System.IServiceProvider serviceProvider, object?[]? arguments);
     public partial class ServiceCollection : Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Collections.Generic.ICollection<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IEnumerable<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.Generic.IList<Microsoft.Extensions.DependencyInjection.ServiceDescriptor>, System.Collections.IEnumerable
@@ -75,6 +94,29 @@ namespace Microsoft.Extensions.DependencyInjection
     }
     public static partial class ServiceCollectionServiceExtensions
     {
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type serviceType, object? serviceKey) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedScoped<TService, TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type serviceType, object? serviceKey) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, object implementationInstance) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, TService implementationInstance) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedSingleton<TService, TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type serviceType, object? serviceKey) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddKeyedTransient<TService, TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type serviceType) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, System.Func<System.IServiceProvider, object> implementationFactory) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Type serviceType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
@@ -103,15 +145,43 @@ namespace Microsoft.Extensions.DependencyInjection
     {
         public ServiceDescriptor(System.Type serviceType, System.Func<System.IServiceProvider, object> factory, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { }
         public ServiceDescriptor(System.Type serviceType, object instance) { }
+        public ServiceDescriptor(System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object?, object> factory, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { }
+        public ServiceDescriptor(System.Type serviceType, object? serviceKey, object instance) { }
+        public ServiceDescriptor(System.Type serviceType, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { }
         public ServiceDescriptor(System.Type serviceType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { }
         public System.Func<System.IServiceProvider, object>? ImplementationFactory { get { throw null; } }
         public object? ImplementationInstance { get { throw null; } }
         [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
         public System.Type? ImplementationType { get { throw null; } }
+        public bool IsKeyedService { get { throw null; } }
+        public System.Func<System.IServiceProvider, object?, object>? KeyedImplementationFactory { get { throw null; } }
+        public object? KeyedImplementationInstance { get { throw null; } }
+        [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)]
+        public System.Type? KeyedImplementationType { get { throw null; } }
         public Microsoft.Extensions.DependencyInjection.ServiceLifetime Lifetime { get { throw null; } }
+        public object? ServiceKey { get { throw null; } }
         public System.Type ServiceType { get { throw null; } }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Describe(System.Type serviceType, System.Func<System.IServiceProvider, object> implementationFactory, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Describe(System.Type serviceType, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor DescribeKeyed(System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor DescribeKeyed(System.Type serviceType, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType, Microsoft.Extensions.DependencyInjection.ServiceLifetime lifetime) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedScoped(System.Type service, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedScoped(System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedScoped<TService>(object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedScoped<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedScoped<TService, TImplementation>(object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton(System.Type serviceType, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton(System.Type serviceType, object? serviceKey, object implementationInstance) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton(System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton<TService>(object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton<TService>(object? serviceKey, TService implementationInstance) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedSingleton<TService, TImplementation>(object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedTransient(System.Type service, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedTransient(System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedTransient<TService>(object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedTransient<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(object? serviceKey) where TService : class where TImplementation : class, TService { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor KeyedTransient<TService, TImplementation>(object? serviceKey, System.Func<System.IServiceProvider, object, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Scoped(System.Type service, System.Func<System.IServiceProvider, object> implementationFactory) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Scoped(System.Type service, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Scoped<TService>(System.Func<System.IServiceProvider, TService> implementationFactory) where TService : class { throw null; }
@@ -131,12 +201,26 @@ namespace Microsoft.Extensions.DependencyInjection
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Transient<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>() where TService : class where TImplementation : class, TService { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceDescriptor Transient<TService, TImplementation>(System.Func<System.IServiceProvider, TImplementation> implementationFactory) where TService : class where TImplementation : class, TService { throw null; }
     }
+    [System.AttributeUsageAttribute(System.AttributeTargets.Parameter)]
+    public partial class ServiceKeyAttribute : System.Attribute
+    {
+        public ServiceKeyAttribute() { }
+    }
     public enum ServiceLifetime
     {
         Singleton = 0,
         Scoped = 1,
         Transient = 2,
     }
+    public static partial class ServiceProviderKeyedServiceExtensions
+    {
+        [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The native code for an IEnumerable<serviceType> might not be available at runtime.")]
+        public static System.Collections.Generic.IEnumerable<object?> GetKeyedServices(this System.IServiceProvider provider, System.Type serviceType, object? serviceKey) { throw null; }
+        public static System.Collections.Generic.IEnumerable<T> GetKeyedServices<T>(this System.IServiceProvider provider, object? serviceKey) { throw null; }
+        public static T? GetKeyedService<T>(this System.IServiceProvider provider, object? serviceKey) { throw null; }
+        public static object GetRequiredKeyedService(this System.IServiceProvider provider, System.Type serviceType, object? serviceKey) { throw null; }
+        public static T GetRequiredKeyedService<T>(this System.IServiceProvider provider, object? serviceKey) where T : notnull { throw null; }
+    }
     public static partial class ServiceProviderServiceExtensions
     {
         public static Microsoft.Extensions.DependencyInjection.AsyncServiceScope CreateAsyncScope(this Microsoft.Extensions.DependencyInjection.IServiceScopeFactory serviceScopeFactory) { throw null; }
@@ -182,5 +266,26 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
         public static void TryAddTransient<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection) where TService : class { }
         public static void TryAddTransient<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, System.Func<System.IServiceProvider, TService> implementationFactory) where TService : class { }
         public static void TryAddTransient<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection) where TService : class where TImplementation : class, TService { }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection RemoveAllKeyed(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type serviceType, object? serviceKey) { throw null; }
+        public static Microsoft.Extensions.DependencyInjection.IServiceCollection RemoveAllKeyed<T>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) { throw null; }
+        public static void TryAddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type service, object? serviceKey) { }
+        public static void TryAddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { }
+        public static void TryAddKeyedScoped(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { }
+        public static void TryAddKeyedScoped<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class { }
+        public static void TryAddKeyedScoped<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { }
+        public static void TryAddKeyedScoped<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class where TImplementation : class, TService { }
+        public static void TryAddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type service, object? serviceKey) { }
+        public static void TryAddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { }
+        public static void TryAddKeyedSingleton(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { }
+        public static void TryAddKeyedSingleton<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class { }
+        public static void TryAddKeyedSingleton<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { }
+        public static void TryAddKeyedSingleton<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey, TService instance) where TService : class { }
+        public static void TryAddKeyedSingleton<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class where TImplementation : class, TService { }
+        public static void TryAddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type service, object? serviceKey) { }
+        public static void TryAddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, System.Func<System.IServiceProvider, object, object> implementationFactory) { }
+        public static void TryAddKeyedTransient(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Type service, object? serviceKey, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type implementationType) { }
+        public static void TryAddKeyedTransient<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class { }
+        public static void TryAddKeyedTransient<TService>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, object? serviceKey, System.Func<System.IServiceProvider, object, TService> implementationFactory) where TService : class { }
+        public static void TryAddKeyedTransient<TService, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, object? serviceKey) where TService : class where TImplementation : class, TService { }
     }
 }
index 30496b0..43e8f56 100644 (file)
@@ -476,6 +476,53 @@ namespace Microsoft.Extensions.DependencyInjection
             return true;
         }
 
+        private static object? GetService(IServiceProvider serviceProvider, ParameterInfo parameterInfo)
+        {
+            // Handle keyed service
+            if (TryGetServiceKey(parameterInfo, out object? key))
+            {
+                if (serviceProvider is IKeyedServiceProvider keyedServiceProvider)
+                {
+                    return keyedServiceProvider.GetKeyedService(parameterInfo.ParameterType, key);
+                }
+                throw new InvalidOperationException(SR.KeyedServicesNotSupported);
+            }
+            // Try non keyed service
+            return serviceProvider.GetService(parameterInfo.ParameterType);
+        }
+
+        private static bool IsService(IServiceProviderIsService serviceProviderIsService, ParameterInfo parameterInfo)
+        {
+            // Handle keyed service
+            if (TryGetServiceKey(parameterInfo, out object? key))
+            {
+                if (serviceProviderIsService is IServiceProviderIsKeyedService serviceProviderIsKeyedService)
+                {
+                    return serviceProviderIsKeyedService.IsKeyedService(parameterInfo.ParameterType, key);
+                }
+                throw new InvalidOperationException(SR.KeyedServicesNotSupported);
+            }
+            // Try non keyed service
+            return serviceProviderIsService.IsService(parameterInfo.ParameterType);
+        }
+
+        private static bool TryGetServiceKey(ParameterInfo parameterInfo, out object? key)
+        {
+            if (parameterInfo.CustomAttributes != null)
+            {
+                foreach (var attribute in parameterInfo.GetCustomAttributes(true))
+                {
+                    if (attribute is FromKeyedServicesAttribute keyed)
+                    {
+                        key = keyed.Key;
+                        return true;
+                    }
+                }
+            }
+            key = null;
+            return false;
+        }
+
         private readonly struct ConstructorMatcher
         {
             private readonly ConstructorInfo _constructor;
@@ -517,7 +564,7 @@ namespace Microsoft.Extensions.DependencyInjection
                 for (int i = 0; i < _parameters.Length; i++)
                 {
                     if (_parameterValues[i] == null &&
-                        !serviceProviderIsService.IsService(_parameters[i].ParameterType))
+                        !IsService(serviceProviderIsService, _parameters[i]))
                     {
                         if (ParameterDefaultValue.TryGetDefaultValue(_parameters[i], out object? defaultValue))
                         {
@@ -539,7 +586,7 @@ namespace Microsoft.Extensions.DependencyInjection
                 {
                     if (_parameterValues[index] == null)
                     {
-                        object? value = provider.GetService(_parameters[index].ParameterType);
+                        object? value = GetService(provider, _parameters[index]);
                         if (value == null)
                         {
                             if (!ParameterDefaultValue.TryGetDefaultValue(_parameters[index], out object? defaultValue))
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.Keyed.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Extensions/ServiceCollectionDescriptorExtensions.Keyed.cs
new file mode 100644 (file)
index 0000000..aad0b4f
--- /dev/null
@@ -0,0 +1,413 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.DependencyInjection.Extensions
+{
+    public static partial class ServiceCollectionDescriptorExtensions
+    {
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedTransient(
+            this IServiceCollection collection,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type service,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+
+            var descriptor = ServiceDescriptor.KeyedTransient(service, serviceKey, service);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// with the <paramref name="implementationType"/> implementation
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        public static void TryAddKeyedTransient(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            var descriptor = ServiceDescriptor.KeyedTransient(service, serviceKey, implementationType);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        public static void TryAddKeyedTransient(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            var descriptor = ServiceDescriptor.KeyedTransient(service, serviceKey, implementationFactory);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedTransient<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedTransient(collection, typeof(TService), serviceKey, typeof(TService));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// implementation type specified in <typeparamref name="TImplementation"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedTransient<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedTransient(collection, typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Transient"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="services"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        public static void TryAddKeyedTransient<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            services.TryAdd(ServiceDescriptor.KeyedTransient(serviceKey, implementationFactory));
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedScoped(
+            this IServiceCollection collection,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type service,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+
+            var descriptor = ServiceDescriptor.KeyedScoped(service, serviceKey, service);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// with the <paramref name="implementationType"/> implementation
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        public static void TryAddKeyedScoped(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            var descriptor = ServiceDescriptor.KeyedScoped(service, serviceKey, implementationType);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        public static void TryAddKeyedScoped(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            var descriptor = ServiceDescriptor.KeyedScoped(service, serviceKey, implementationFactory);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedScoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedScoped(collection, typeof(TService), serviceKey, typeof(TService));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// implementation type specified in <typeparamref name="TImplementation"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedScoped<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedScoped(collection, typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Scoped"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="services"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedScoped<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            services.TryAdd(ServiceDescriptor.KeyedScoped(serviceKey, implementationFactory));
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedSingleton(
+            this IServiceCollection collection,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type service,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+
+            var descriptor = ServiceDescriptor.KeyedSingleton(service, serviceKey, service);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// with the <paramref name="implementationType"/> implementation
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        public static void TryAddKeyedSingleton(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            var descriptor = ServiceDescriptor.KeyedSingleton(service, serviceKey, implementationType);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <paramref name="service"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="service">The type of the service to register.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        public static void TryAddKeyedSingleton(
+            this IServiceCollection collection,
+            Type service,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            var descriptor = ServiceDescriptor.KeyedSingleton(service, serviceKey, implementationFactory);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedSingleton(collection, typeof(TService), serviceKey, typeof(TService));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// implementation type specified in <typeparamref name="TImplementation"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        public static void TryAddKeyedSingleton<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection collection, object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(collection);
+
+            TryAddKeyedSingleton(collection, typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// with an instance specified in <paramref name="instance"/>
+        /// to the <paramref name="collection"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="instance">The instance of the service to add.</param>
+        public static void TryAddKeyedSingleton<TService>(this IServiceCollection collection, object? serviceKey, TService instance)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(collection);
+            ThrowHelper.ThrowIfNull(instance);
+
+            var descriptor = ServiceDescriptor.KeyedSingleton(serviceType: typeof(TService), serviceKey, implementationInstance: instance);
+            ServiceCollectionDescriptorExtensions.TryAdd(collection, descriptor);
+        }
+
+        /// <summary>
+        /// Adds the specified <typeparamref name="TService"/> as a <see cref="ServiceLifetime.Singleton"/> service
+        /// using the factory specified in <paramref name="implementationFactory"/>
+        /// to the <paramref name="services"/> if the service type hasn't already been registered.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        public static void TryAddKeyedSingleton<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            services.TryAdd(ServiceDescriptor.KeyedSingleton(serviceKey, implementationFactory));
+        }
+
+        /// <summary>
+        /// Removes all services of type <typeparamref name="T"/> in <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <returns>The <see cref="IServiceCollection"/> for chaining.</returns>
+        public static IServiceCollection RemoveAllKeyed<T>(this IServiceCollection collection, object? serviceKey)
+        {
+            return RemoveAllKeyed(collection, typeof(T), serviceKey);
+        }
+
+        /// <summary>
+        /// Removes all services of type <paramref name="serviceType"/> in <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="collection">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="serviceType">The service type to remove.</param>
+        /// <param name="serviceKey">The service key.</param>
+        /// <returns>The <see cref="IServiceCollection"/> for chaining.</returns>
+        public static IServiceCollection RemoveAllKeyed(this IServiceCollection collection, Type serviceType, object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            for (int i = collection.Count - 1; i >= 0; i--)
+            {
+                ServiceDescriptor? descriptor = collection[i];
+                if (descriptor.ServiceType == serviceType && descriptor.ServiceKey == serviceKey)
+                {
+                    collection.RemoveAt(i);
+                }
+            }
+
+            return collection;
+        }
+    }
+}
index 4f99cd9..f2435dc 100644 (file)
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
     /// <summary>
     /// Extension methods for adding and removing services to an <see cref="IServiceCollection" />.
     /// </summary>
-    public static class ServiceCollectionDescriptorExtensions
+    public static partial class ServiceCollectionDescriptorExtensions
     {
         /// <summary>
         /// Adds the specified <paramref name="descriptor"/> to the <paramref name="collection"/>.
@@ -66,7 +66,8 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
             int count = collection.Count;
             for (int i = 0; i < count; i++)
             {
-                if (collection[i].ServiceType == descriptor.ServiceType)
+                if (collection[i].ServiceType == descriptor.ServiceType
+                    && collection[i].ServiceKey == descriptor.ServiceKey)
                 {
                     // Already added
                     return;
@@ -411,7 +412,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
             ThrowHelper.ThrowIfNull(collection);
             ThrowHelper.ThrowIfNull(instance);
 
-            var descriptor = ServiceDescriptor.Singleton(typeof(TService), instance);
+            var descriptor = ServiceDescriptor.Singleton(serviceType: typeof(TService), implementationInstance: instance);
             TryAdd(collection, descriptor);
         }
 
@@ -472,7 +473,8 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
             {
                 ServiceDescriptor service = services[i];
                 if (service.ServiceType == descriptor.ServiceType &&
-                    service.GetImplementationType() == implementationType)
+                    service.GetImplementationType() == implementationType &&
+                    service.ServiceKey == descriptor.ServiceKey)
                 {
                     // Already added
                     return;
@@ -530,7 +532,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
             int count = collection.Count;
             for (int i = 0; i < count; i++)
             {
-                if (collection[i].ServiceType == descriptor.ServiceType)
+                if (collection[i].ServiceType == descriptor.ServiceType && collection[i].ServiceKey == descriptor.ServiceKey)
                 {
                     collection.RemoveAt(i);
                     break;
@@ -564,7 +566,7 @@ namespace Microsoft.Extensions.DependencyInjection.Extensions
             for (int i = collection.Count - 1; i >= 0; i--)
             {
                 ServiceDescriptor? descriptor = collection[i];
-                if (descriptor.ServiceType == serviceType)
+                if (descriptor.ServiceType == serviceType && descriptor.ServiceKey == null)
                 {
                     collection.RemoveAt(i);
                 }
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/FromKeyedServicesAttribute.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/FromKeyedServicesAttribute.cs
new file mode 100644 (file)
index 0000000..a7ff22e
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    [AttributeUsage(AttributeTargets.Parameter)]
+    public class FromKeyedServicesAttribute : Attribute
+    {
+        public FromKeyedServicesAttribute(object key) => Key = key;
+
+        public object Key { get; }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IKeyedServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IKeyedServiceProvider.cs
new file mode 100644 (file)
index 0000000..ff6814e
--- /dev/null
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public interface IKeyedServiceProvider : IServiceProvider
+    {
+        /// <summary>
+        /// Gets the service object of the specified type.
+        /// </summary>
+        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns> A service object of type serviceType. -or- null if there is no service object of type serviceType.</returns>
+        object? GetKeyedService(Type serviceType, object? serviceKey);
+
+        /// <summary>
+        /// Gets service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/> implementing
+        /// this interface.
+        /// </summary>
+        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A service object of type <paramref name="serviceType"/>.
+        /// Throws an exception if the <see cref="IServiceProvider"/> cannot create the object.</returns>
+        object GetRequiredKeyedService(Type serviceType, object? serviceKey);
+    }
+
+    public static class KeyedService
+    {
+        public static object AnyKey { get; } = new AnyKeyObj();
+
+        private sealed class AnyKeyObj
+        {
+            public override string? ToString() => "*";
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IServiceProviderIsKeyedService.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/IServiceProviderIsKeyedService.cs
new file mode 100644 (file)
index 0000000..39cda6b
--- /dev/null
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public interface IServiceProviderIsKeyedService : IServiceProviderIsService
+    {
+        /// <summary>
+        /// Determines if the specified service type is available from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <param name="serviceType">An object that specifies the type of service object to test.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>true if the specified service is a available, false if it is not.</returns>
+        bool IsKeyedService(Type serviceType, object? serviceKey);
+    }
+}
index bfe0f89..d9eb294 100644 (file)
     <value>Multiple constructors accepting all given argument types have been found in type '{0}'. There should only be one applicable constructor.</value>
     <comment>{0} = instance type</comment>
   </data>
+  <data name="KeyedServicesNotSupported" xml:space="preserve">
+    <value>This service provider doesn't support keyed services.</value>
+  </data>
+  <data name="KeyedDescriptorMisuse" xml:space="preserve">
+    <value>This service descriptor is keyed. Your service provider may not support keyed services.</value>
+  </data>
+  <data name="NonKeyedDescriptorMisuse" xml:space="preserve">
+    <value>This service descriptor is not keyed.</value>
+  </data>
 </root>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceCollectionServiceExtensions.Keyed.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceCollectionServiceExtensions.Keyed.cs
new file mode 100644 (file)
index 0000000..788b20f
--- /dev/null
@@ -0,0 +1,561 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    /// <summary>
+    /// Extension methods for adding services to an <see cref="IServiceCollection" />.
+    /// </summary>
+    public static partial class ServiceCollectionServiceExtensions
+    {
+        /// <summary>
+        /// Adds a transient service of the type specified in <paramref name="serviceType"/> with an
+        /// implementation of the type specified in <paramref name="implementationType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationType, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <paramref name="serviceType"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationFactory, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedTransient(typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <paramref name="serviceType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register and the implementation to use.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient(
+            this IServiceCollection services,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            return services.AddKeyedTransient(serviceType, serviceKey, serviceType);
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <typeparamref name="TService"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedTransient(typeof(TService), serviceKey);
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <typeparamref name="TService"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedTransient(typeof(TService), serviceKey, implementationFactory);
+        }
+
+        /// <summary>
+        /// Adds a transient service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation" /> using the
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Transient"/>
+        public static IServiceCollection AddKeyedTransient<TService, TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedTransient(typeof(TService), serviceKey, implementationFactory);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <paramref name="serviceType"/> with an
+        /// implementation of the type specified in <paramref name="implementationType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationType, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <paramref name="serviceType"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationFactory, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedScoped(typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <paramref name="serviceType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register and the implementation to use.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped(
+            this IServiceCollection services,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            return services.AddKeyedScoped(serviceType, serviceKey, serviceType);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <typeparamref name="TService"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedScoped(typeof(TService), serviceKey);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <typeparamref name="TService"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedScoped(typeof(TService), serviceKey, implementationFactory);
+        }
+
+        /// <summary>
+        /// Adds a scoped service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation" /> using the
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Scoped"/>
+        public static IServiceCollection AddKeyedScoped<TService, TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedScoped(typeof(TService), serviceKey, implementationFactory);
+        }
+
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <paramref name="serviceType"/> with an
+        /// implementation of the type specified in <paramref name="implementationType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The implementation type of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationType, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <paramref name="serviceType"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return AddKeyed(services, serviceType, serviceKey, implementationFactory, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedSingleton(typeof(TService), serviceKey, typeof(TImplementation));
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <paramref name="serviceType"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register and the implementation to use.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton(
+            this IServiceCollection services,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type serviceType,
+            object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            return services.AddKeyedSingleton(serviceType, serviceKey, serviceType);
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <typeparamref name="TService"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(
+            this IServiceCollection services,
+            object? serviceKey)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+
+            return services.AddKeyedSingleton(typeof(TService), serviceKey, typeof(TService));
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <typeparamref name="TService"/> with a
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedSingleton(typeof(TService), serviceKey, implementationFactory);
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <typeparamref name="TService"/> with an
+        /// implementation type specified in <typeparamref name="TImplementation" /> using the
+        /// factory specified in <paramref name="implementationFactory"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service to add.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">The factory that creates the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton<TService, TImplementation>(
+            this IServiceCollection services,
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return services.AddKeyedSingleton(typeof(TService), serviceKey, implementationFactory);
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <paramref name="serviceType"/> with an
+        /// instance specified in <paramref name="implementationInstance"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceType">The type of the service to register.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationInstance">The instance of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton(
+            this IServiceCollection services,
+            Type serviceType,
+            object? serviceKey,
+            object implementationInstance)
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationInstance);
+
+            var serviceDescriptor = new ServiceDescriptor(serviceType, serviceKey, implementationInstance);
+            services.Add(serviceDescriptor);
+            return services;
+        }
+
+        /// <summary>
+        /// Adds a singleton service of the type specified in <typeparamref name="TService" /> with an
+        /// instance specified in <paramref name="implementationInstance"/> to the
+        /// specified <see cref="IServiceCollection"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationInstance">The instance of the service.</param>
+        /// <returns>A reference to this instance after the operation has completed.</returns>
+        /// <seealso cref="ServiceLifetime.Singleton"/>
+        public static IServiceCollection AddKeyedSingleton<TService>(
+            this IServiceCollection services,
+            object? serviceKey,
+            TService implementationInstance)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(services);
+            ThrowHelper.ThrowIfNull(implementationInstance);
+
+            return services.AddKeyedSingleton(typeof(TService), serviceKey, implementationInstance);
+        }
+
+        private static IServiceCollection AddKeyed(
+            IServiceCollection collection,
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
+            ServiceLifetime lifetime)
+        {
+            var descriptor = new ServiceDescriptor(serviceType, serviceKey, implementationType, lifetime);
+            collection.Add(descriptor);
+            return collection;
+        }
+
+        private static IServiceCollection AddKeyed(
+            IServiceCollection collection,
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory,
+            ServiceLifetime lifetime)
+        {
+            var descriptor = new ServiceDescriptor(serviceType, serviceKey, implementationFactory, lifetime);
+            collection.Add(descriptor);
+            return collection;
+        }
+    }
+}
index c07bde1..1542751 100644 (file)
@@ -9,7 +9,7 @@ namespace Microsoft.Extensions.DependencyInjection
     /// <summary>
     /// Extension methods for adding services to an <see cref="IServiceCollection" />.
     /// </summary>
-    public static class ServiceCollectionServiceExtensions
+    public static partial class ServiceCollectionServiceExtensions
     {
         /// <summary>
         /// Adds a transient service of the type specified in <paramref name="serviceType"/> with an
index 5489892..4edb491 100644 (file)
@@ -23,12 +23,41 @@ namespace Microsoft.Extensions.DependencyInjection
             Type serviceType,
             [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
             ServiceLifetime lifetime)
-            : this(serviceType, lifetime)
+            : this(serviceType, null, implementationType, lifetime)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ServiceDescriptor"/> with the specified <paramref name="implementationType"/>.
+        /// </summary>
+        /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The <see cref="Type"/> implementing the service.</param>
+        /// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service.</param>
+        public ServiceDescriptor(
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
+            ServiceLifetime lifetime)
+            : this(serviceType, serviceKey, lifetime)
         {
             ThrowHelper.ThrowIfNull(serviceType);
             ThrowHelper.ThrowIfNull(implementationType);
 
-            ImplementationType = implementationType;
+            _implementationType = implementationType;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ServiceDescriptor"/> with the specified <paramref name="instance"/>
+        /// as a <see cref="ServiceLifetime.Singleton"/>.
+        /// </summary>
+        /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
+        /// <param name="instance">The instance implementing the service.</param>
+        public ServiceDescriptor(
+            Type serviceType,
+            object instance)
+            : this(serviceType, null, instance)
+        {
         }
 
         /// <summary>
@@ -36,16 +65,18 @@ namespace Microsoft.Extensions.DependencyInjection
         /// as a <see cref="ServiceLifetime.Singleton"/>.
         /// </summary>
         /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
         /// <param name="instance">The instance implementing the service.</param>
         public ServiceDescriptor(
             Type serviceType,
+            object? serviceKey,
             object instance)
-            : this(serviceType, ServiceLifetime.Singleton)
+            : this(serviceType, serviceKey, ServiceLifetime.Singleton)
         {
             ThrowHelper.ThrowIfNull(serviceType);
             ThrowHelper.ThrowIfNull(instance);
 
-            ImplementationInstance = instance;
+            _implementationInstance = instance;
         }
 
         /// <summary>
@@ -58,18 +89,48 @@ namespace Microsoft.Extensions.DependencyInjection
             Type serviceType,
             Func<IServiceProvider, object> factory,
             ServiceLifetime lifetime)
-            : this(serviceType, lifetime)
+            : this(serviceType, serviceKey: null, lifetime)
+        {
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(factory);
+
+            _implementationFactory = factory;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="ServiceDescriptor"/> with the specified <paramref name="factory"/>.
+        /// </summary>
+        /// <param name="serviceType">The <see cref="Type"/> of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="factory">A factory used for creating service instances.</param>
+        /// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service.</param>
+        public ServiceDescriptor(
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> factory,
+            ServiceLifetime lifetime)
+            : this(serviceType, serviceKey, lifetime)
         {
             ThrowHelper.ThrowIfNull(serviceType);
             ThrowHelper.ThrowIfNull(factory);
 
-            ImplementationFactory = factory;
+            if (serviceKey is null)
+            {
+                // If the key is null, use the same factory signature as non-keyed descriptor
+                Func<IServiceProvider, object> nullKeyedFactory = sp => factory(sp, null);
+                _implementationFactory = nullKeyedFactory;
+            }
+            else
+            {
+                _implementationFactory = factory;
+            }
         }
 
-        private ServiceDescriptor(Type serviceType, ServiceLifetime lifetime)
+        private ServiceDescriptor(Type serviceType, object? serviceKey, ServiceLifetime lifetime)
         {
             Lifetime = lifetime;
             ServiceType = serviceType;
+            ServiceKey = serviceKey;
         }
 
         /// <summary>
@@ -78,64 +139,195 @@ namespace Microsoft.Extensions.DependencyInjection
         public ServiceLifetime Lifetime { get; }
 
         /// <summary>
+        /// Get the key of the service, if applicable.
+        /// </summary>
+        public object? ServiceKey { get; }
+
+        /// <summary>
         /// Gets the <see cref="Type"/> of the service.
         /// </summary>
         public Type ServiceType { get; }
 
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+        private Type? _implementationType;
+
+        /// <summary>
+        /// Gets the <see cref="Type"/> that implements the service.
+        /// </summary>
+        [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+        public Type? ImplementationType
+        {
+            get
+            {
+                if (IsKeyedService)
+                {
+                    ThrowKeyedDescriptor();
+                }
+                return _implementationType;
+            }
+        }
+
         /// <summary>
         /// Gets the <see cref="Type"/> that implements the service.
         /// </summary>
         [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
-        public Type? ImplementationType { get; }
+        public Type? KeyedImplementationType
+        {
+            get
+            {
+                if (!IsKeyedService)
+                {
+                    ThrowNonKeyedDescriptor();
+                }
+                return _implementationType;
+            }
+        }
+
+        private object? _implementationInstance;
+
+        /// <summary>
+        /// Gets the instance that implements the service.
+        /// </summary>
+        public object? ImplementationInstance
+        {
+            get
+            {
+                if (IsKeyedService)
+                {
+                    ThrowKeyedDescriptor();
+                }
+                return _implementationInstance;
+            }
+        }
 
         /// <summary>
         /// Gets the instance that implements the service.
         /// </summary>
-        public object? ImplementationInstance { get; }
+        public object? KeyedImplementationInstance
+        {
+            get
+            {
+                if (!IsKeyedService)
+                {
+                    ThrowNonKeyedDescriptor();
+                }
+                return _implementationInstance;
+            }
+        }
+
+        private object? _implementationFactory;
 
         /// <summary>
         /// Gets the factory used for creating service instances.
         /// </summary>
-        public Func<IServiceProvider, object>? ImplementationFactory { get; }
+        public Func<IServiceProvider, object>? ImplementationFactory
+        {
+            get
+            {
+                if (IsKeyedService)
+                {
+                    ThrowKeyedDescriptor();
+                }
+                return (Func<IServiceProvider, object>?) _implementationFactory;
+            }
+        }
+
+        /// <summary>
+        /// Gets the factory used for creating Keyed service instances.
+        /// </summary>
+        public Func<IServiceProvider, object?, object>? KeyedImplementationFactory
+        {
+            get
+            {
+                if (!IsKeyedService)
+                {
+                    ThrowNonKeyedDescriptor();
+                }
+                return (Func<IServiceProvider, object?, object>?) _implementationFactory;
+            }
+        }
+
+        public bool IsKeyedService => ServiceKey != null;
 
         /// <inheritdoc />
         public override string ToString()
         {
             string? lifetime = $"{nameof(ServiceType)}: {ServiceType} {nameof(Lifetime)}: {Lifetime} ";
 
-            if (ImplementationType != null)
+            if (IsKeyedService)
             {
-                return lifetime + $"{nameof(ImplementationType)}: {ImplementationType}";
-            }
+                lifetime += $"{nameof(ServiceKey)}: {ServiceKey} ";
 
-            if (ImplementationFactory != null)
-            {
-                return lifetime + $"{nameof(ImplementationFactory)}: {ImplementationFactory.Method}";
+                if (KeyedImplementationType != null)
+                {
+                    return lifetime + $"{nameof(KeyedImplementationType)}: {KeyedImplementationType}";
+                }
+
+                if (KeyedImplementationFactory != null)
+                {
+                    return lifetime + $"{nameof(KeyedImplementationFactory)}: {KeyedImplementationFactory.Method}";
+                }
+
+                return lifetime + $"{nameof(KeyedImplementationInstance)}: {KeyedImplementationInstance}";
             }
+            else
+            {
+                if (ImplementationType != null)
+                {
+                    return lifetime + $"{nameof(ImplementationType)}: {ImplementationType}";
+                }
+
+                if (ImplementationFactory != null)
+                {
+                    return lifetime + $"{nameof(ImplementationFactory)}: {ImplementationFactory.Method}";
+                }
 
-            return lifetime + $"{nameof(ImplementationInstance)}: {ImplementationInstance}";
+                return lifetime + $"{nameof(ImplementationInstance)}: {ImplementationInstance}";
+            }
         }
 
         internal Type GetImplementationType()
         {
-            if (ImplementationType != null)
-            {
-                return ImplementationType;
-            }
-            else if (ImplementationInstance != null)
+            if (ServiceKey == null)
             {
-                return ImplementationInstance.GetType();
+                if (ImplementationType != null)
+                {
+                    return ImplementationType;
+                }
+                else if (ImplementationInstance != null)
+                {
+                    return ImplementationInstance.GetType();
+                }
+                else if (ImplementationFactory != null)
+                {
+                    Type[]? typeArguments = ImplementationFactory.GetType().GenericTypeArguments;
+
+                    Debug.Assert(typeArguments.Length == 2);
+
+                    return typeArguments[1];
+                }
             }
-            else if (ImplementationFactory != null)
+            else
             {
-                Type[]? typeArguments = ImplementationFactory.GetType().GenericTypeArguments;
+                if (KeyedImplementationType != null)
+                {
+                    return KeyedImplementationType;
+                }
+                else if (KeyedImplementationInstance != null)
+                {
+                    return KeyedImplementationInstance.GetType();
+                }
+                else if (KeyedImplementationFactory != null)
+                {
+                    Type[]? typeArguments = KeyedImplementationFactory.GetType().GenericTypeArguments;
 
-                Debug.Assert(typeArguments.Length == 2);
+                    Debug.Assert(typeArguments.Length == 3);
 
-                return typeArguments[1];
+                    return typeArguments[2];
+                }
             }
 
-            Debug.Assert(false, "ImplementationType, ImplementationInstance or ImplementationFactory must be non null");
+            Debug.Assert(false, "ImplementationType, ImplementationInstance, ImplementationFactory or KeyedImplementationFactory must be non null");
             return null;
         }
 
@@ -151,7 +343,23 @@ namespace Microsoft.Extensions.DependencyInjection
             where TService : class
             where TImplementation : class, TService
         {
-            return Describe<TService, TImplementation>(ServiceLifetime.Transient);
+            return DescribeKeyed<TService, TImplementation>(null, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedTransient<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            return DescribeKeyed<TService, TImplementation>(serviceKey, ServiceLifetime.Transient);
         }
 
         /// <summary>
@@ -174,6 +382,26 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="service"/> and <paramref name="implementationType"/>
+        /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
+        /// </summary>
+        /// <param name="service">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The type of the implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedTransient(
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            return DescribeKeyed(service, serviceKey, implementationType, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
         /// <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
@@ -194,6 +422,28 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedTransient<TService, TImplementation>(
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
         /// </summary>
@@ -210,6 +460,23 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedTransient<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <paramref name="service"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
         /// </summary>
@@ -226,6 +493,23 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="service"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Transient"/> lifetime.
+        /// </summary>
+        /// <param name="service">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedTransient(Type service, object? serviceKey, Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(service, serviceKey, implementationFactory, ServiceLifetime.Transient);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
         /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
         /// </summary>
@@ -236,7 +520,23 @@ namespace Microsoft.Extensions.DependencyInjection
             where TService : class
             where TImplementation : class, TService
         {
-            return Describe<TService, TImplementation>(ServiceLifetime.Scoped);
+            return DescribeKeyed<TService, TImplementation>(null, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedScoped<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            return DescribeKeyed<TService, TImplementation>(serviceKey, ServiceLifetime.Scoped);
         }
 
         /// <summary>
@@ -256,6 +556,23 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="service"/> and <paramref name="implementationType"/>
+        /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
+        /// </summary>
+        /// <param name="service">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The type of the implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedScoped(
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            return DescribeKeyed(service, serviceKey, implementationType, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
         /// <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
@@ -276,6 +593,28 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedScoped<TService, TImplementation>(
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
         /// </summary>
@@ -292,6 +631,23 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedScoped<TService>(object? serviceKey, Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <paramref name="service"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
         /// </summary>
@@ -308,6 +664,23 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="service"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
+        /// </summary>
+        /// <param name="service">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedScoped(Type service, object? serviceKey, Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(service, serviceKey, implementationFactory, ServiceLifetime.Scoped);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
         /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
         /// </summary>
@@ -318,7 +691,24 @@ namespace Microsoft.Extensions.DependencyInjection
             where TService : class
             where TImplementation : class, TService
         {
-            return Describe<TService, TImplementation>(ServiceLifetime.Singleton);
+            return DescribeKeyed<TService, TImplementation>(null, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
+            object? serviceKey)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            return DescribeKeyed<TService, TImplementation>(serviceKey, ServiceLifetime.Singleton);
         }
 
         /// <summary>
@@ -341,6 +731,26 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="service"/> and <paramref name="implementationType"/>
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <param name="service">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The type of the implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton(
+            Type service,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
+        {
+            ThrowHelper.ThrowIfNull(service);
+            ThrowHelper.ThrowIfNull(implementationType);
+
+            return DescribeKeyed(service, serviceKey, implementationType, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
         /// <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
@@ -361,6 +771,28 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <typeparamref name="TImplementation"/>,
+        /// <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <typeparam name="TImplementation">The type of the implementation.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton<TService, TImplementation>(
+            object? serviceKey,
+            Func<IServiceProvider, object?, TImplementation> implementationFactory)
+            where TService : class
+            where TImplementation : class, TService
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
         /// </summary>
@@ -377,6 +809,25 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton<TService>(
+            object? serviceKey,
+            Func<IServiceProvider, object?, TService> implementationFactory)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(typeof(TService), serviceKey, implementationFactory, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
         /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
         /// </summary>
@@ -395,6 +846,26 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <param name="serviceType">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton(
+            Type serviceType,
+            object? serviceKey,
+            Func<IServiceProvider, object?, object> implementationFactory)
+        {
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationFactory);
+
+            return DescribeKeyed(serviceType, serviceKey, implementationFactory, ServiceLifetime.Singleton);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <typeparamref name="TService"/>, <paramref name="implementationInstance"/>,
         /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
         /// </summary>
@@ -406,7 +877,26 @@ namespace Microsoft.Extensions.DependencyInjection
         {
             ThrowHelper.ThrowIfNull(implementationInstance);
 
-            return Singleton(typeof(TService), implementationInstance);
+            return Singleton(serviceType: typeof(TService), implementationInstance: implementationInstance);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <typeparamref name="TService"/>, <paramref name="implementationInstance"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <typeparam name="TService">The type of the service.</typeparam>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationInstance">The instance of the implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton<TService>(
+            object? serviceKey,
+            TService implementationInstance)
+            where TService : class
+        {
+            ThrowHelper.ThrowIfNull(implementationInstance);
+
+            return KeyedSingleton(typeof(TService), serviceKey, implementationInstance);
         }
 
         /// <summary>
@@ -427,12 +917,35 @@ namespace Microsoft.Extensions.DependencyInjection
             return new ServiceDescriptor(serviceType, implementationInstance);
         }
 
-        private static ServiceDescriptor Describe<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(ServiceLifetime lifetime)
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="serviceType"/>, <paramref name="implementationInstance"/>,
+        /// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
+        /// </summary>
+        /// <param name="serviceType">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationInstance">The instance of the implementation.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor KeyedSingleton(
+            Type serviceType,
+            object? serviceKey,
+            object implementationInstance)
+        {
+            ThrowHelper.ThrowIfNull(serviceType);
+            ThrowHelper.ThrowIfNull(implementationInstance);
+
+            return new ServiceDescriptor(serviceType, serviceKey, implementationInstance);
+        }
+
+        private static ServiceDescriptor DescribeKeyed<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
+            object? serviceKey,
+            ServiceLifetime lifetime)
             where TService : class
             where TImplementation : class, TService
         {
-            return Describe(
+            return DescribeKeyed(
                 typeof(TService),
+                serviceKey,
                 typeof(TImplementation),
                 lifetime: lifetime);
         }
@@ -456,6 +969,25 @@ namespace Microsoft.Extensions.DependencyInjection
 
         /// <summary>
         /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="serviceType"/>, <paramref name="implementationType"/>,
+        /// and <paramref name="lifetime"/>.
+        /// </summary>
+        /// <param name="serviceType">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationType">The type of the implementation.</param>
+        /// <param name="lifetime">The lifetime of the service.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor DescribeKeyed(
+            Type serviceType,
+            object? serviceKey,
+            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
+            ServiceLifetime lifetime)
+        {
+            return new ServiceDescriptor(serviceType, serviceKey, implementationType, lifetime);
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
         /// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
         /// and <paramref name="lifetime"/>.
         /// </summary>
@@ -468,25 +1000,63 @@ namespace Microsoft.Extensions.DependencyInjection
             return new ServiceDescriptor(serviceType, implementationFactory, lifetime);
         }
 
+        /// <summary>
+        /// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
+        /// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
+        /// and <paramref name="lifetime"/>.
+        /// </summary>
+        /// <param name="serviceType">The type of the service.</param>
+        /// <param name="serviceKey">The <see cref="ServiceDescriptor.ServiceKey"/> of the service.</param>
+        /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
+        /// <param name="lifetime">The lifetime of the service.</param>
+        /// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
+        public static ServiceDescriptor DescribeKeyed(Type serviceType, object? serviceKey, Func<IServiceProvider, object?, object> implementationFactory, ServiceLifetime lifetime)
+        {
+            return new ServiceDescriptor(serviceType, serviceKey, implementationFactory, lifetime);
+        }
+
         private string DebuggerToString()
         {
             string debugText = $@"Lifetime = {Lifetime}, ServiceType = ""{ServiceType.FullName}""";
 
             // Either implementation type, factory or instance is set.
-            if (ImplementationType != null)
-            {
-                debugText += $@", ImplementationType = ""{ImplementationType.FullName}""";
-            }
-            else if (ImplementationFactory != null)
+            if (IsKeyedService)
             {
-                debugText += $@", ImplementationFactory = {ImplementationFactory.Method}";
+                debugText += $@", ServiceKey = ""{ServiceKey}""";
+                if (KeyedImplementationType != null)
+                {
+                    debugText += $@", KeyedImplementationType = ""{KeyedImplementationType.FullName}""";
+                }
+                else if (KeyedImplementationFactory != null)
+                {
+                    debugText += $@", KeyedImplementationFactory = {KeyedImplementationFactory.Method}";
+                }
+                else
+                {
+                    debugText += $@", KeyedImplementationInstance = {KeyedImplementationInstance}";
+                }
             }
             else
             {
-                debugText += $@", ImplementationInstance = {ImplementationInstance}";
+                if (ImplementationType != null)
+                {
+                    debugText += $@", ImplementationType = ""{ImplementationType.FullName}""";
+                }
+                else if (ImplementationFactory != null)
+                {
+                    debugText += $@", ImplementationFactory = {ImplementationFactory.Method}";
+                }
+                else
+                {
+                    debugText += $@", ImplementationInstance = {ImplementationInstance}";
+                }
             }
 
             return debugText;
         }
+
+        private static void ThrowKeyedDescriptor() => throw new InvalidOperationException(SR.KeyedDescriptorMisuse);
+
+        private static void ThrowNonKeyedDescriptor() => throw new InvalidOperationException(SR.NonKeyedDescriptorMisuse);
     }
 }
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceKeyAttribute.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceKeyAttribute.cs
new file mode 100644 (file)
index 0000000..f9dfc98
--- /dev/null
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    [AttributeUsage(AttributeTargets.Parameter)]
+    public class ServiceKeyAttribute : Attribute
+    {
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderKeyedServiceExtensions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderKeyedServiceExtensions.cs
new file mode 100644 (file)
index 0000000..4ae52a0
--- /dev/null
@@ -0,0 +1,101 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    /// <summary>
+    /// Extension methods for getting services from an <see cref="IServiceProvider" />.
+    /// </summary>
+    public static class ServiceProviderKeyedServiceExtensions
+    {
+        /// <summary>
+        /// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of service object to get.</typeparam>
+        /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns>A service object of type <typeparamref name="T"/> or null if there is no such service.</returns>
+        public static T? GetKeyedService<T>(this IServiceProvider provider, object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(provider);
+
+            if (provider is IKeyedServiceProvider keyedServiceProvider)
+            {
+                return (T?)keyedServiceProvider.GetKeyedService(typeof(T), serviceKey);
+            }
+
+            throw new InvalidOperationException(SR.KeyedServicesNotSupported);
+        }
+
+        /// <summary>
+        /// Get service of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
+        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns>A service object of type <paramref name="serviceType"/>.</returns>
+        /// <exception cref="System.InvalidOperationException">There is no service of type <paramref name="serviceType"/>.</exception>
+        public static object GetRequiredKeyedService(this IServiceProvider provider, Type serviceType, object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(provider);
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            if (provider is IKeyedServiceProvider requiredServiceSupportingProvider)
+            {
+                return requiredServiceSupportingProvider.GetRequiredKeyedService(serviceType, serviceKey);
+            }
+
+            throw new InvalidOperationException(SR.KeyedServicesNotSupported);
+        }
+
+        /// <summary>
+        /// Get service of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of service object to get.</typeparam>
+        /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the service object from.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns>A service object of type <typeparamref name="T"/>.</returns>
+        /// <exception cref="System.InvalidOperationException">There is no service of type <typeparamref name="T"/>.</exception>
+        public static T GetRequiredKeyedService<T>(this IServiceProvider provider, object? serviceKey) where T : notnull
+        {
+            ThrowHelper.ThrowIfNull(provider);
+
+            return (T)provider.GetRequiredKeyedService(typeof(T), serviceKey);
+        }
+
+        /// <summary>
+        /// Get an enumeration of services of type <typeparamref name="T"/> from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of service object to get.</typeparam>
+        /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns>An enumeration of services of type <typeparamref name="T"/>.</returns>
+        public static IEnumerable<T> GetKeyedServices<T>(this IServiceProvider provider, object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(provider);
+
+            return provider.GetRequiredKeyedService<IEnumerable<T>>(serviceKey);
+        }
+
+        /// <summary>
+        /// Get an enumeration of services of type <paramref name="serviceType"/> from the <see cref="IServiceProvider"/>.
+        /// </summary>
+        /// <param name="provider">The <see cref="IServiceProvider"/> to retrieve the services from.</param>
+        /// <param name="serviceType">An object that specifies the type of service object to get.</param>
+        /// <param name="serviceKey">An object that specifies the key of service object to get.</param>
+        /// <returns>An enumeration of services of type <paramref name="serviceType"/>.</returns>
+        [RequiresDynamicCode("The native code for an IEnumerable<serviceType> might not be available at runtime.")]
+        public static IEnumerable<object?> GetKeyedServices(this IServiceProvider provider, Type serviceType, object? serviceKey)
+        {
+            ThrowHelper.ThrowIfNull(provider);
+            ThrowHelper.ThrowIfNull(serviceType);
+
+            Type? genericEnumerable = typeof(IEnumerable<>).MakeGenericType(serviceType);
+            return (IEnumerable<object>)provider.GetRequiredKeyedService(genericEnumerable, serviceKey);
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs
new file mode 100644 (file)
index 0000000..3aa3282
--- /dev/null
@@ -0,0 +1,362 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Xunit;
+using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
+using System.Linq;
+using System.Security.Cryptography;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification
+{
+    public abstract partial class KeyedDependencyInjectionSpecificationTests
+    {
+        protected abstract  IServiceProvider CreateServiceProvider(IServiceCollection collection);
+
+        [Fact]
+        public void ResolveKeyedService()
+        {
+            var service1 = new Service();
+            var service2 = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>("service1", service1);
+            serviceCollection.AddKeyedSingleton<IService>("service2", service2);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            Assert.Same(service1, provider.GetKeyedService<IService>("service1"));
+            Assert.Same(service2, provider.GetKeyedService<IService>("service2"));
+        }
+
+        [Fact]
+        public void ResolveNullKeyedService()
+        {
+            var service1 = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>(null, service1);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            var nonKeyed = provider.GetService<IService>();
+            var nullKey = provider.GetKeyedService<IService>(null);
+
+            Assert.Same(service1, nonKeyed);
+            Assert.Same(service1, nullKey);
+        }
+
+        [Fact]
+        public void ResolveNonKeyedService()
+        {
+            var service1 = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IService>(service1);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            var nonKeyed = provider.GetService<IService>();
+            var nullKey = provider.GetKeyedService<IService>(null);
+
+            Assert.Same(service1, nonKeyed);
+            Assert.Same(service1, nullKey);
+        }
+
+        [Fact]
+        public void ResolveKeyedOpenGenericService()
+        {
+            var collection = new ServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeOpenGenericService<>), "my-service", typeof(FakeOpenGenericService<>));
+            collection.AddSingleton<IFakeSingletonService, FakeService>();
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var genericService = provider.GetKeyedService<IFakeOpenGenericService<IFakeSingletonService>>("my-service");
+            var singletonService = provider.GetService<IFakeSingletonService>();
+
+            // Assert
+            Assert.Same(singletonService, genericService.Value);
+        }
+
+        [Fact]
+        public void ResolveKeyedServices()
+        {
+            var service1 = new Service();
+            var service2 = new Service();
+            var service3 = new Service();
+            var service4 = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>("first-service", service1);
+            serviceCollection.AddKeyedSingleton<IService>("service", service2);
+            serviceCollection.AddKeyedSingleton<IService>("service", service3);
+            serviceCollection.AddKeyedSingleton<IService>("service", service4);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            var firstSvc = provider.GetKeyedServices<IService>("first-service").ToList();
+            Assert.Single(firstSvc);
+            Assert.Same(service1, firstSvc[0]);
+
+            var services = provider.GetKeyedServices<IService>("service").ToList();
+            Assert.Equal(new[] { service2, service3, service4 }, services);
+        }
+
+        [Fact]
+        public void ResolveKeyedGenericServices()
+        {
+            var service1 = new FakeService();
+            var service2 = new FakeService();
+            var service3 = new FakeService();
+            var service4 = new FakeService();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("first-service", service1);
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service2);
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service3);
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("service", service4);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            var firstSvc = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("first-service").ToList();
+            Assert.Single(firstSvc);
+            Assert.Same(service1, firstSvc[0]);
+
+            var services = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("service").ToList();
+            Assert.Equal(new[] { service2, service3, service4 }, services);
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonInstance()
+        {
+            var service = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>("service1", service);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            Assert.Same(service, provider.GetKeyedService<IService>("service1"));
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonInstanceWithKeyInjection()
+        {
+            var serviceKey = "this-is-my-service";
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService, Service>(serviceKey);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            var svc = provider.GetKeyedService<IService>(serviceKey);
+            Assert.NotNull(svc);
+            Assert.Equal(serviceKey, svc.ToString());
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonInstanceWithAnyKey()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService, Service>(KeyedService.AnyKey);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+
+            var serviceKey1 = "some-key";
+            var svc1 = provider.GetKeyedService<IService>(serviceKey1);
+            Assert.NotNull(svc1);
+            Assert.Equal(serviceKey1, svc1.ToString());
+
+            var serviceKey2 = "some-other-key";
+            var svc2 = provider.GetKeyedService<IService>(serviceKey2);
+            Assert.NotNull(svc2);
+            Assert.Equal(serviceKey2, svc2.ToString());
+        }
+
+        [Fact]
+        public void ResolveKeyedServicesSingletonInstanceWithAnyKey()
+        {
+            var service1 = new FakeService();
+            var service2 = new FakeService();
+
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>(KeyedService.AnyKey, service1);
+            serviceCollection.AddKeyedSingleton<IFakeOpenGenericService<PocoClass>>("some-key", service2);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            var services = provider.GetKeyedServices<IFakeOpenGenericService<PocoClass>>("some-key").ToList();
+            Assert.Equal(new[] { service1, service2 }, services);
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonInstanceWithKeyedParameter()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService, Service>("service1");
+            serviceCollection.AddKeyedSingleton<IService, Service>("service2");
+            serviceCollection.AddSingleton<OtherService>();
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            var svc = provider.GetService<OtherService>();
+            Assert.NotNull(svc);
+            Assert.Equal("service1", svc.Service1.ToString());
+            Assert.Equal("service2", svc.Service2.ToString());
+        }
+
+        [Fact]
+        public void CreateServiceWithKeyedParameter()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IService, Service>();
+            serviceCollection.AddKeyedSingleton<IService, Service>("service1");
+            serviceCollection.AddKeyedSingleton<IService, Service>("service2");
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<OtherService>());
+            var svc = ActivatorUtilities.CreateInstance<OtherService>(provider);
+            Assert.NotNull(svc);
+            Assert.Equal("service1", svc.Service1.ToString());
+            Assert.Equal("service2", svc.Service2.ToString());
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonFactory()
+        {
+            var service = new Service();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>("service1", (sp, key) => service);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            Assert.Same(service, provider.GetKeyedService<IService>("service1"));
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonFactoryWithAnyKey()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService>(KeyedService.AnyKey, (sp, key) => new Service((string)key));
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+
+            for (int i=0; i<3; i++)
+            {
+                var key = "service" + i;
+                var s1 = provider.GetKeyedService<IService>(key);
+                var s2 = provider.GetKeyedService<IService>(key);
+                Assert.Same(s1, s2);
+                Assert.Equal(key, s1.ToString());
+            }
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonFactoryWithAnyKeyIgnoreWrongType()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedTransient<IService, ServiceWithIntKey>(KeyedService.AnyKey);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            Assert.NotNull(provider.GetKeyedService<IService>(87));
+            Assert.ThrowsAny<InvalidOperationException>(() => provider.GetKeyedService<IService>(new object()));
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceSingletonType()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedSingleton<IService, Service>("service1");
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            Assert.Equal(typeof(Service), provider.GetKeyedService<IService>("service1")!.GetType());
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceTransientFactory()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedTransient<IService>("service1", (sp, key) => new Service(key as string));
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            var first = provider.GetKeyedService<IService>("service1");
+            var second = provider.GetKeyedService<IService>("service1");
+            Assert.NotSame(first, second);
+            Assert.Equal("service1", first.ToString());
+            Assert.Equal("service1", second.ToString());
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceTransientType()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedTransient<IService, Service>("service1");
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            var first = provider.GetKeyedService<IService>("service1");
+            var second = provider.GetKeyedService<IService>("service1");
+            Assert.NotSame(first, second);
+        }
+
+        [Fact]
+        public void ResolveKeyedServiceTransientTypeWithAnyKey()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddKeyedTransient<IService, Service>(KeyedService.AnyKey);
+
+            var provider = CreateServiceProvider(serviceCollection);
+
+            Assert.Null(provider.GetService<IService>());
+            var first = provider.GetKeyedService<IService>("service1");
+            var second = provider.GetKeyedService<IService>("service1");
+            Assert.NotSame(first, second);
+        }
+
+        internal interface IService { }
+
+        internal class Service : IService
+        {
+            private readonly string _id;
+
+            public Service() => _id = Guid.NewGuid().ToString();
+
+            public Service([ServiceKey] string id) => _id = id;
+
+            public override string? ToString() => _id;
+        }
+
+        internal class OtherService
+        {
+            public OtherService(
+                [FromKeyedServices("service1")] IService service1,
+                [FromKeyedServices("service2")] IService service2)
+            {
+                Service1 = service1;
+                Service2 = service2;
+            }
+
+            public IService Service1 { get; }
+
+            public IService Service2 { get; }
+        }
+
+        internal class ServiceWithIntKey : IService
+        {
+            private readonly int _id;
+
+            public ServiceWithIntKey([ServiceKey] int id) => _id = id;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ServiceProviderIsKeyedServiceSpecificationTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ServiceProviderIsKeyedServiceSpecificationTests.cs
new file mode 100644 (file)
index 0000000..7321fe0
--- /dev/null
@@ -0,0 +1,129 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
+using Xunit;
+
+namespace Microsoft.Extensions.DependencyInjection.Specification
+{
+    public abstract partial class KeyedDependencyInjectionSpecificationTests
+    {
+        public virtual bool SupportsIServiceProviderIsKeyedService => true;
+
+        [Fact]
+        public void ExplicitServiceRegistrationWithIsKeyedService()
+        {
+            if (!SupportsIServiceProviderIsKeyedService)
+            {
+                return;
+            }
+
+            // Arrange
+            var key = new object();
+            var collection = new TestServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeService), key, typeof(FakeService));
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>();
+
+            // Assert
+            Assert.NotNull(serviceProviderIsService);
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeService), key));
+            Assert.False(serviceProviderIsService.IsKeyedService(typeof(FakeService), new object()));
+        }
+
+        [Fact]
+        public void OpenGenericsWithIsKeyedService()
+        {
+            if (!SupportsIServiceProviderIsKeyedService)
+            {
+                return;
+            }
+
+            // Arrange
+            var key = new object();
+            var collection = new TestServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeOpenGenericService<>), key, typeof(FakeOpenGenericService<>));
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>();
+
+            // Assert
+            Assert.NotNull(serviceProviderIsService);
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<IFakeService>), key));
+            Assert.False(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<>), new object()));
+        }
+
+        [Fact]
+        public void ClosedGenericsWithIsKeyedService()
+        {
+            if (!SupportsIServiceProviderIsKeyedService)
+            {
+                return;
+            }
+
+            // Arrange
+            var key = new object();
+            var collection = new TestServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeOpenGenericService<IFakeService>), key, typeof(FakeOpenGenericService<IFakeService>));
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>();
+
+            // Assert
+            Assert.NotNull(serviceProviderIsService);
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeOpenGenericService<IFakeService>), key));
+        }
+
+        [Fact]
+        public void IEnumerableWithIsKeyedServiceAlwaysReturnsTrue()
+        {
+            if (!SupportsIServiceProviderIsKeyedService)
+            {
+                return;
+            }
+
+            // Arrange
+            var key = new object();
+            var collection = new TestServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeService), key, typeof(FakeService));
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>();
+
+            // Assert
+            Assert.NotNull(serviceProviderIsService);
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<IFakeService>), key));
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<FakeService>), key));
+            Assert.False(serviceProviderIsService.IsKeyedService(typeof(IEnumerable<>), new object()));
+        }
+
+        [Fact]
+        public void NonKeyedServiceWithIsKeyedService()
+        {
+            if (!SupportsIServiceProviderIsKeyedService)
+            {
+                return;
+            }
+
+            // Arrange
+            var collection = new TestServiceCollection();
+            collection.AddKeyedTransient(typeof(IFakeService), null, typeof(FakeService));
+            var provider = CreateServiceProvider(collection);
+
+            // Act
+            var serviceProviderIsService = provider.GetService<IServiceProviderIsKeyedService>();
+
+            // Assert
+            Assert.NotNull(serviceProviderIsService);
+            Assert.True(serviceProviderIsService.IsKeyedService(typeof(IFakeService), null));
+        }
+    }
+}
index 0b85156..8c7be14 100644 (file)
@@ -19,11 +19,13 @@ namespace Microsoft.Extensions.DependencyInjection
         public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; }
         public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; }
     }
-    public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider
+    public sealed partial class ServiceProvider : Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, System.IAsyncDisposable, System.IDisposable, System.IServiceProvider
     {
         internal ServiceProvider() { }
         public void Dispose() { }
         public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
+        public object? GetKeyedService(System.Type serviceType, object? serviceKey) { throw null; }
+        public object GetRequiredKeyedService(System.Type serviceType, object? serviceKey) { throw null; }
         public object? GetService(System.Type serviceType) { throw null; }
     }
     public partial class ServiceProviderOptions
index b12dff0..c258be1 100644 (file)
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.Tracing;
 using System.Linq.Expressions;
@@ -243,20 +244,27 @@ namespace Microsoft.Extensions.DependencyInjection
             builder.Append(descriptor.Lifetime);
             builder.Append("\", ");
 
-            if (descriptor.ImplementationType is not null)
+            if (descriptor.HasImplementationType())
             {
                 builder.Append("\"implementationType\": \"");
-                builder.Append(descriptor.ImplementationType);
+                builder.Append(descriptor.GetImplementationType());
             }
-            else if (descriptor.ImplementationFactory is not null)
+            else if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null)
             {
                 builder.Append("\"implementationFactory\": \"");
                 builder.Append(descriptor.ImplementationFactory.Method);
             }
-            else if (descriptor.ImplementationInstance is not null)
+            else if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null)
             {
+                builder.Append("\"implementationFactory\": \"");
+                builder.Append(descriptor.KeyedImplementationFactory.Method);
+            }
+            else if (descriptor.HasImplementationInstance())
+            {
+                object? instance = descriptor.GetImplementationInstance();
+                Debug.Assert(instance != null, "descriptor.ImplementationInstance != null");
                 builder.Append("\"implementationInstance\": \"");
-                builder.Append(descriptor.ImplementationInstance.GetType());
+                builder.Append(instance.GetType());
                 builder.Append(" (instance)");
             }
             else
index a81b637..e6f57dc 100644 (file)
   <data name="AotCannotCreateGenericValueType" xml:space="preserve">
     <value>Unable to create a generic service for type '{0}' because '{1}' is a ValueType. Native code to support creating generic services might not be available with native AOT.</value>
   </data>
+  <data name="NoServiceRegistered" xml:space="preserve">
+    <value>No service for type '{0}' has been registered.</value>
+  </data>
+  <data name="InvalidServiceKeyType" xml:space="preserve">
+    <value>The type of the key used for lookup doesn't match the type in the constructor parameter with the ServiceKey attribute.</value>
+  </data>
 </root>
\ No newline at end of file
index 3163b22..6fcba89 100644 (file)
@@ -10,58 +10,58 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
     internal sealed class CallSiteChain
     {
-        private readonly Dictionary<Type, ChainItemInfo> _callSiteChain;
+        private readonly Dictionary<ServiceIdentifier, ChainItemInfo> _callSiteChain;
 
         public CallSiteChain()
         {
-            _callSiteChain = new Dictionary<Type, ChainItemInfo>();
+            _callSiteChain = new Dictionary<ServiceIdentifier, ChainItemInfo>();
         }
 
-        public void CheckCircularDependency(Type serviceType)
+        public void CheckCircularDependency(ServiceIdentifier serviceIdentifier)
         {
-            if (_callSiteChain.ContainsKey(serviceType))
+            if (_callSiteChain.ContainsKey(serviceIdentifier))
             {
-                throw new InvalidOperationException(CreateCircularDependencyExceptionMessage(serviceType));
+                throw new InvalidOperationException(CreateCircularDependencyExceptionMessage(serviceIdentifier));
             }
         }
 
-        public void Remove(Type serviceType)
+        public void Remove(ServiceIdentifier serviceIdentifier)
         {
-            _callSiteChain.Remove(serviceType);
+            _callSiteChain.Remove(serviceIdentifier);
         }
 
-        public void Add(Type serviceType, Type? implementationType = null)
+        public void Add(ServiceIdentifier serviceIdentifier, Type? implementationType = null)
         {
-            _callSiteChain[serviceType] = new ChainItemInfo(_callSiteChain.Count, implementationType);
+            _callSiteChain[serviceIdentifier] = new ChainItemInfo(_callSiteChain.Count, implementationType);
         }
 
-        private string CreateCircularDependencyExceptionMessage(Type type)
+        private string CreateCircularDependencyExceptionMessage(ServiceIdentifier serviceIdentifier)
         {
             var messageBuilder = new StringBuilder();
-            messageBuilder.Append(SR.Format(SR.CircularDependencyException, TypeNameHelper.GetTypeDisplayName(type)));
+            messageBuilder.Append(SR.Format(SR.CircularDependencyException, TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType)));
             messageBuilder.AppendLine();
 
-            AppendResolutionPath(messageBuilder, type);
+            AppendResolutionPath(messageBuilder, serviceIdentifier);
 
             return messageBuilder.ToString();
         }
 
-        private void AppendResolutionPath(StringBuilder builder, Type currentlyResolving)
+        private void AppendResolutionPath(StringBuilder builder, ServiceIdentifier currentlyResolving)
         {
-            var ordered = new List<KeyValuePair<Type, ChainItemInfo>>(_callSiteChain);
+            var ordered = new List<KeyValuePair<ServiceIdentifier, ChainItemInfo>>(_callSiteChain);
             ordered.Sort((a, b) => a.Value.Order.CompareTo(b.Value.Order));
 
-            foreach (KeyValuePair<Type, ChainItemInfo> pair in ordered)
+            foreach (KeyValuePair<ServiceIdentifier, ChainItemInfo> pair in ordered)
             {
-                Type serviceType = pair.Key;
+                ServiceIdentifier serviceIdentifier = pair.Key;
                 Type? implementationType = pair.Value.ImplementationType;
-                if (implementationType == null || serviceType == implementationType)
+                if (implementationType == null || serviceIdentifier.ServiceType == implementationType)
                 {
-                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceType));
+                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType));
                 }
                 else
                 {
-                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceType))
+                    builder.Append(TypeNameHelper.GetTypeDisplayName(serviceIdentifier.ServiceType))
                            .Append('(')
                            .Append(TypeNameHelper.GetTypeDisplayName(implementationType))
                            .Append(')');
@@ -70,7 +70,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 builder.Append(" -> ");
             }
 
-            builder.Append(TypeNameHelper.GetTypeDisplayName(currentlyResolving));
+            builder.Append(TypeNameHelper.GetTypeDisplayName(currentlyResolving.ServiceType));
         }
 
         private readonly struct ChainItemInfo
index 908d467..3034843 100644 (file)
@@ -11,13 +11,13 @@ using Microsoft.Extensions.Internal;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    internal sealed class CallSiteFactory : IServiceProviderIsService
+    internal sealed class CallSiteFactory : IServiceProviderIsService, IServiceProviderIsKeyedService
     {
         private const int DefaultSlot = 0;
         private readonly ServiceDescriptor[] _descriptors;
         private readonly ConcurrentDictionary<ServiceCacheKey, ServiceCallSite> _callSiteCache = new ConcurrentDictionary<ServiceCacheKey, ServiceCallSite>();
-        private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new Dictionary<Type, ServiceDescriptorCacheItem>();
-        private readonly ConcurrentDictionary<Type, object> _callSiteLocks = new ConcurrentDictionary<Type, object>();
+        private readonly Dictionary<ServiceIdentifier, ServiceDescriptorCacheItem> _descriptorLookup = new Dictionary<ServiceIdentifier, ServiceDescriptorCacheItem>();
+        private readonly ConcurrentDictionary<ServiceIdentifier, object> _callSiteLocks = new ConcurrentDictionary<ServiceIdentifier, object>();
 
         private readonly StackGuard _stackGuard;
 
@@ -39,7 +39,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 Type serviceType = descriptor.ServiceType;
                 if (serviceType.IsGenericTypeDefinition)
                 {
-                    Type? implementationType = descriptor.ImplementationType;
+                    Type? implementationType = descriptor.GetImplementationType();
 
                     if (implementationType == null || !implementationType.IsGenericTypeDefinition)
                     {
@@ -67,10 +67,10 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                         ValidateTrimmingAnnotations(serviceType, serviceTypeGenericArguments, implementationType, implementationTypeGenericArguments);
                     }
                 }
-                else if (descriptor.ImplementationInstance == null && descriptor.ImplementationFactory == null)
+                else if (!descriptor.HasImplementationInstance() && !descriptor.HasImplementationFactory())
                 {
-                    Debug.Assert(descriptor.ImplementationType != null);
-                    Type implementationType = descriptor.ImplementationType;
+                    Type? implementationType = descriptor.GetImplementationType();
+                    Debug.Assert(implementationType != null);
 
                     if (implementationType.IsGenericTypeDefinition ||
                         implementationType.IsAbstract ||
@@ -81,7 +81,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     }
                 }
 
-                Type cacheKey = serviceType;
+                var cacheKey = ServiceIdentifier.FromDescriptor(descriptor);
                 _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem cacheItem);
                 _descriptorLookup[cacheKey] = cacheItem.Add(descriptor);
             }
@@ -152,7 +152,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         // For unit testing
         internal int? GetSlot(ServiceDescriptor serviceDescriptor)
         {
-            if (_descriptorLookup.TryGetValue(serviceDescriptor.ServiceType, out ServiceDescriptorCacheItem item))
+            if (_descriptorLookup.TryGetValue(ServiceIdentifier.FromDescriptor(serviceDescriptor), out ServiceDescriptorCacheItem item))
             {
                 return item.GetSlot(serviceDescriptor);
             }
@@ -160,26 +160,27 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             return null;
         }
 
-        internal ServiceCallSite? GetCallSite(Type serviceType, CallSiteChain callSiteChain) =>
-            _callSiteCache.TryGetValue(new ServiceCacheKey(serviceType, DefaultSlot), out ServiceCallSite? site) ? site :
-            CreateCallSite(serviceType, callSiteChain);
+        internal ServiceCallSite? GetCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain) =>
+            _callSiteCache.TryGetValue(new ServiceCacheKey(serviceIdentifier, DefaultSlot), out ServiceCallSite? site) ? site :
+            CreateCallSite(serviceIdentifier, callSiteChain);
 
         internal ServiceCallSite? GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
         {
-            if (_descriptorLookup.TryGetValue(serviceDescriptor.ServiceType, out ServiceDescriptorCacheItem descriptor))
+            var serviceIdentifier = ServiceIdentifier.FromDescriptor(serviceDescriptor);
+            if (_descriptorLookup.TryGetValue(serviceIdentifier, out ServiceDescriptorCacheItem descriptor))
             {
-                return TryCreateExact(serviceDescriptor, serviceDescriptor.ServiceType, callSiteChain, descriptor.GetSlot(serviceDescriptor));
+                return TryCreateExact(serviceDescriptor, serviceIdentifier, callSiteChain, descriptor.GetSlot(serviceDescriptor));
             }
 
             Debug.Fail("_descriptorLookup didn't contain requested serviceDescriptor");
             return null;
         }
 
-        private ServiceCallSite? CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
+        private ServiceCallSite? CreateCallSite(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
         {
             if (!_stackGuard.TryEnterOnCurrentStack())
             {
-                return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceType, callSiteChain);
+                return _stackGuard.RunOnEmptyStack(CreateCallSite, serviceIdentifier, callSiteChain);
             }
 
             // We need to lock the resolution process for a single service type at a time:
@@ -192,44 +193,67 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
             // This is to make sure we can safely store singleton values on the callsites themselves
 
-            var callsiteLock = _callSiteLocks.GetOrAdd(serviceType, static _ => new object());
+            var callsiteLock = _callSiteLocks.GetOrAdd(serviceIdentifier, static _ => new object());
 
             lock (callsiteLock)
             {
-                callSiteChain.CheckCircularDependency(serviceType);
+                callSiteChain.CheckCircularDependency(serviceIdentifier);
 
-                ServiceCallSite? callSite = TryCreateExact(serviceType, callSiteChain) ??
-                                           TryCreateOpenGeneric(serviceType, callSiteChain) ??
-                                           TryCreateEnumerable(serviceType, callSiteChain);
+                ServiceCallSite? callSite = TryCreateExact(serviceIdentifier, callSiteChain) ??
+                                           TryCreateOpenGeneric(serviceIdentifier, callSiteChain) ??
+                                           TryCreateEnumerable(serviceIdentifier, callSiteChain);
 
                 return callSite;
             }
         }
 
-        private ServiceCallSite? TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
+        private ServiceCallSite? TryCreateExact(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
         {
-            if (_descriptorLookup.TryGetValue(serviceType, out ServiceDescriptorCacheItem descriptor))
+            if (_descriptorLookup.TryGetValue(serviceIdentifier, out ServiceDescriptorCacheItem descriptor))
             {
-                return TryCreateExact(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
+                return TryCreateExact(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot);
+            }
+
+            if (serviceIdentifier.ServiceKey != null)
+            {
+                // Check if there is a registration with KeyedService.AnyKey
+                var catchAllIdentifier = new ServiceIdentifier(KeyedService.AnyKey, serviceIdentifier.ServiceType);
+                if (_descriptorLookup.TryGetValue(catchAllIdentifier, out descriptor))
+                {
+                    return TryCreateExact(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot);
+                }
             }
 
             return null;
         }
 
-        private ServiceCallSite? TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain)
+        private ServiceCallSite? TryCreateOpenGeneric(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
         {
-            if (serviceType.IsConstructedGenericType
-                && _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out ServiceDescriptorCacheItem descriptor))
+            if (serviceIdentifier.IsConstructedGenericType)
             {
-                return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot, true);
+                var genericIdentifier = serviceIdentifier.GetGenericTypeDefinition();
+                if (_descriptorLookup.TryGetValue(genericIdentifier, out ServiceDescriptorCacheItem descriptor))
+                {
+                    return TryCreateOpenGeneric(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot, true);
+                }
+
+                if (serviceIdentifier.ServiceKey != null)
+                {
+                    // Check if there is a registration with KeyedService.AnyKey
+                    var catchAllIdentifier = new ServiceIdentifier(KeyedService.AnyKey, genericIdentifier.ServiceType);
+                    if (_descriptorLookup.TryGetValue(catchAllIdentifier, out descriptor))
+                    {
+                        return TryCreateOpenGeneric(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot, true);
+                    }
+                }
             }
 
             return null;
         }
 
-        private ServiceCallSite? TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
+        private ServiceCallSite? TryCreateEnumerable(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain)
         {
-            ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, DefaultSlot);
+            ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, DefaultSlot);
             if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
             {
                 return serviceCallSite;
@@ -237,7 +261,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
             try
             {
-                callSiteChain.Add(serviceType);
+                callSiteChain.Add(serviceIdentifier);
+
+                var serviceType = serviceIdentifier.ServiceType;
 
                 if (!serviceType.IsConstructedGenericType ||
                     serviceType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
@@ -246,6 +272,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 }
 
                 Type itemType = serviceType.GenericTypeArguments[0];
+                var cacheKey = new ServiceIdentifier(serviceIdentifier.ServiceKey, itemType);
                 if (ServiceProvider.VerifyAotCompatibility && itemType.IsValueType)
                 {
                     // NativeAOT apps are not able to make Enumerable of ValueType services
@@ -258,7 +285,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
                 // If item type is not generic we can safely use descriptor cache
                 if (!itemType.IsConstructedGenericType &&
-                    _descriptorLookup.TryGetValue(itemType, out ServiceDescriptorCacheItem descriptors))
+                    _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem descriptors))
                 {
                     callSites = new ServiceCallSite[descriptors.Count];
                     for (int i = 0; i < descriptors.Count; i++)
@@ -268,7 +295,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                         // Last service should get slot 0
                         int slot = descriptors.Count - i - 1;
                         // There may not be any open generics here
-                        ServiceCallSite? callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot);
+                        ServiceCallSite? callSite = TryCreateExact(descriptor, cacheKey, callSiteChain, slot);
                         Debug.Assert(callSite != null);
 
                         cacheLocation = GetCommonCacheLocation(cacheLocation, callSite.Cache.Location);
@@ -288,16 +315,22 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     int slot = 0;
                     for (int i = _descriptors.Length - 1; i >= 0; i--)
                     {
-                        if (TryCreateExact(_descriptors[i], itemType, callSiteChain, slot) is { } callSite)
+                        if (KeysMatch(_descriptors[i].ServiceKey, cacheKey.ServiceKey))
                         {
-                            AddCallSite(callSite, i);
+                            if (TryCreateExact(_descriptors[i], cacheKey, callSiteChain, slot) is { } callSite)
+                            {
+                                AddCallSite(callSite, i);
+                            }
                         }
                     }
                     for (int i = _descriptors.Length - 1; i >= 0; i--)
                     {
-                        if (TryCreateOpenGeneric(_descriptors[i], itemType, callSiteChain, slot, throwOnConstraintViolation: false) is { } callSite)
+                        if (KeysMatch(_descriptors[i].ServiceKey, cacheKey.ServiceKey))
                         {
-                            AddCallSite(callSite, i);
+                            if (TryCreateOpenGeneric(_descriptors[i], cacheKey, callSiteChain, slot, throwOnConstraintViolation: false) is { } callSite)
+                            {
+                                AddCallSite(callSite, i);
+                            }
                         }
                     }
 
@@ -323,7 +356,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             }
             finally
             {
-                callSiteChain.Remove(serviceType);
+                callSiteChain.Remove(serviceIdentifier);
             }
         }
 
@@ -332,34 +365,39 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             return (CallSiteResultCacheLocation)Math.Max((int)locationA, (int)locationB);
         }
 
-        private ServiceCallSite? TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
+        private ServiceCallSite? TryCreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot)
         {
-            if (serviceType == descriptor.ServiceType)
+            if (serviceIdentifier.ServiceType == descriptor.ServiceType)
             {
-                ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);
+                ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot);
                 if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
                 {
                     return serviceCallSite;
                 }
 
                 ServiceCallSite callSite;
-                var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
-                if (descriptor.ImplementationInstance != null)
+                var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);
+                if (descriptor.HasImplementationInstance())
                 {
-                    callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);
+                    callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.GetImplementationInstance());
                 }
-                else if (descriptor.ImplementationFactory != null)
+                else if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null)
                 {
                     callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);
                 }
-                else if (descriptor.ImplementationType != null)
+                else if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null)
+                {
+                    callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, serviceIdentifier.ServiceKey!, descriptor.KeyedImplementationFactory);
+                }
+                else if (descriptor.HasImplementationType())
                 {
-                    callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
+                    callSite = CreateConstructorCallSite(lifetime, serviceIdentifier, descriptor.GetImplementationType()!, callSiteChain);
                 }
                 else
                 {
                     throw new InvalidOperationException(SR.InvalidServiceDescriptor);
                 }
+                callSite.Key = descriptor.ServiceKey;
 
                 return _callSiteCache[callSiteKey] = callSite;
             }
@@ -374,29 +412,30 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
             Justification = "When ServiceProvider.VerifyAotCompatibility is true, which it is by default when PublishAot=true, " +
             "this method ensures the generic types being created aren't using ValueTypes.")]
-        private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
+        private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation)
         {
-            if (serviceType.IsConstructedGenericType &&
-                serviceType.GetGenericTypeDefinition() == descriptor.ServiceType)
+            if (serviceIdentifier.IsConstructedGenericType &&
+                serviceIdentifier.ServiceType.GetGenericTypeDefinition() == descriptor.ServiceType)
             {
-                ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);
+                ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot);
                 if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
                 {
                     return serviceCallSite;
                 }
 
-                Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null");
-                var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
+                Type? implementationType = descriptor.GetImplementationType();
+                Debug.Assert(implementationType != null, "descriptor.ImplementationType != null");
+                var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot);
                 Type closedType;
                 try
                 {
-                    Type[] genericTypeArguments = serviceType.GenericTypeArguments;
+                    Type[] genericTypeArguments = serviceIdentifier.ServiceType.GenericTypeArguments;
                     if (ServiceProvider.VerifyAotCompatibility)
                     {
-                        VerifyOpenGenericAotCompatibility(serviceType, genericTypeArguments);
+                        VerifyOpenGenericAotCompatibility(serviceIdentifier.ServiceType, genericTypeArguments);
                     }
 
-                    closedType = descriptor.ImplementationType.MakeGenericType(genericTypeArguments);
+                    closedType = implementationType.MakeGenericType(genericTypeArguments);
                 }
                 catch (ArgumentException)
                 {
@@ -408,7 +447,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     return null;
                 }
 
-                return _callSiteCache[callSiteKey] = CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
+                return _callSiteCache[callSiteKey] = CreateConstructorCallSite(lifetime, serviceIdentifier, closedType, callSiteChain);
             }
 
             return null;
@@ -416,13 +455,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
         private ConstructorCallSite CreateConstructorCallSite(
             ResultCache lifetime,
-            Type serviceType,
+            ServiceIdentifier serviceIdentifier,
             [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,
             CallSiteChain callSiteChain)
         {
             try
             {
-                callSiteChain.Add(serviceType, implementationType);
+                callSiteChain.Add(serviceIdentifier, implementationType);
                 ConstructorInfo[] constructors = implementationType.GetConstructors();
 
                 ServiceCallSite[]? parameterCallSites = null;
@@ -437,16 +476,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     ParameterInfo[] parameters = constructor.GetParameters();
                     if (parameters.Length == 0)
                     {
-                        return new ConstructorCallSite(lifetime, serviceType, constructor);
+                        return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor);
                     }
 
                     parameterCallSites = CreateArgumentCallSites(
+                        serviceIdentifier,
                         implementationType,
                         callSiteChain,
                         parameters,
                         throwIfCallSiteNotFound: true)!;
 
-                    return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites);
+                    return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites);
                 }
 
                 Array.Sort(constructors,
@@ -459,6 +499,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     ParameterInfo[] parameters = constructors[i].GetParameters();
 
                     ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites(
+                        serviceIdentifier,
                         implementationType,
                         callSiteChain,
                         parameters,
@@ -509,27 +550,53 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 else
                 {
                     Debug.Assert(parameterCallSites != null);
-                    return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites);
+                    return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, bestConstructor, parameterCallSites);
                 }
             }
             finally
             {
-                callSiteChain.Remove(serviceType);
+                callSiteChain.Remove(serviceIdentifier);
             }
         }
 
         /// <returns>Not <b>null</b> if <b>throwIfCallSiteNotFound</b> is true</returns>
         private ServiceCallSite[]? CreateArgumentCallSites(
+            ServiceIdentifier serviceIdentifier,
             Type implementationType,
             CallSiteChain callSiteChain,
             ParameterInfo[] parameters,
             bool throwIfCallSiteNotFound)
         {
             var parameterCallSites = new ServiceCallSite[parameters.Length];
+
             for (int index = 0; index < parameters.Length; index++)
             {
+                ServiceCallSite? callSite = null;
                 Type parameterType = parameters[index].ParameterType;
-                ServiceCallSite? callSite = GetCallSite(parameterType, callSiteChain);
+                if (parameters[index].CustomAttributes != null)
+                {
+                    foreach (var attribute in parameters[index].GetCustomAttributes(true))
+                    {
+                        if (serviceIdentifier.ServiceKey != null && attribute is ServiceKeyAttribute)
+                        {
+                            // Check if the parameter type matches
+                            if (parameterType != serviceIdentifier.ServiceKey.GetType())
+                            {
+                                throw new InvalidOperationException(SR.InvalidServiceKeyType);
+                            }
+                            callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey);
+                            break;
+                        }
+                        if (attribute is FromKeyedServicesAttribute keyed)
+                        {
+                            var parameterSvcId = new ServiceIdentifier(keyed.Key, parameterType);
+                            callSite = GetCallSite(parameterSvcId, callSiteChain);
+                            break;
+                        }
+                    }
+                }
+
+                callSite ??= GetCallSite(ServiceIdentifier.FromServiceType(parameterType), callSiteChain);
 
                 if (callSite == null && ParameterDefaultValue.TryGetDefaultValue(parameters[index], out object? defaultValue))
                 {
@@ -573,13 +640,19 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             }
         }
 
-        public void Add(Type type, ServiceCallSite serviceCallSite)
+        public void Add(ServiceIdentifier serviceIdentifier, ServiceCallSite serviceCallSite)
         {
-            _callSiteCache[new ServiceCacheKey(type, DefaultSlot)] = serviceCallSite;
+            _callSiteCache[new ServiceCacheKey(serviceIdentifier, DefaultSlot)] = serviceCallSite;
         }
 
-        public bool IsService(Type serviceType)
+        public bool IsService(Type serviceType) => IsService(new ServiceIdentifier(null, serviceType));
+
+        public bool IsKeyedService(Type serviceType, object? key) => IsService(new ServiceIdentifier(key, serviceType));
+
+        internal bool IsService(ServiceIdentifier serviceIdentifier)
         {
+            var serviceType = serviceIdentifier.ServiceType;
+
             if (serviceType is null)
             {
                 throw new ArgumentNullException(nameof(serviceType));
@@ -591,7 +664,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 return false;
             }
 
-            if (_descriptorLookup.ContainsKey(serviceType))
+            if (_descriptorLookup.ContainsKey(serviceIdentifier))
+            {
+                return true;
+            }
+
+            if (serviceIdentifier.ServiceKey != null && _descriptorLookup.ContainsKey(new ServiceIdentifier(KeyedService.AnyKey, serviceType)))
             {
                 return true;
             }
@@ -600,14 +678,29 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             {
                 // We special case IEnumerable since it isn't explicitly registered in the container
                 // yet we can manifest instances of it when requested.
-                return genericDefinition == typeof(IEnumerable<>) || _descriptorLookup.ContainsKey(genericDefinition);
+                return genericDefinition == typeof(IEnumerable<>) || _descriptorLookup.ContainsKey(serviceIdentifier.GetGenericTypeDefinition());
             }
 
             // These are the built in service types that aren't part of the list of service descriptors
             // If you update these make sure to also update the code in ServiceProvider.ctor
             return serviceType == typeof(IServiceProvider) ||
                    serviceType == typeof(IServiceScopeFactory) ||
-                   serviceType == typeof(IServiceProviderIsService);
+                   serviceType == typeof(IServiceProviderIsService) ||
+                   serviceType == typeof(IServiceProviderIsKeyedService);
+        }
+
+        /// <summary>
+        /// Returns true if both keys are null or equals, or if key1 is KeyedService.AnyKey and key2 is not null
+        /// </summary>
+        private static bool KeysMatch(object? key1, object? key2)
+        {
+            if (key1 == null && key2 == null)
+                return true;
+
+            if (key1 != null && key2 != null)
+                return key1.Equals(KeyedService.AnyKey) || key1.Equals(key2);
+
+            return false;
         }
 
         private struct ServiceDescriptorCacheItem
index f228507..f979f14 100644 (file)
@@ -7,7 +7,7 @@ using System.Diagnostics.CodeAnalysis;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
-    internal sealed class CallSiteValidator: CallSiteVisitor<CallSiteValidator.CallSiteValidatorState, Type?>
+    internal sealed class CallSiteValidator : CallSiteVisitor<CallSiteValidator.CallSiteValidatorState, Type?>
     {
         // Keys are services being resolved via GetService, values - first scoped service in their call site tree
         private readonly ConcurrentDictionary<ServiceCacheKey, Type> _scopedServices = new ConcurrentDictionary<ServiceCacheKey, Type>();
@@ -30,13 +30,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 if (serviceType == scopedService)
                 {
                     throw new InvalidOperationException(
-                        SR.Format(SR.DirectScopedResolvedFromRootException, serviceType,
+                        SR.Format(SR.DirectScopedResolvedFromRootException, callSite.ServiceType,
                             nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
                 }
 
                 throw new InvalidOperationException(
                     SR.Format(SR.ScopedResolvedFromRootException,
-                        serviceType,
+                        callSite.ServiceType,
                         scopedService,
                         nameof(ServiceLifetime.Scoped).ToLowerInvariant()));
             }
index 3386987..e7fea17 100644 (file)
@@ -15,6 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             ServiceType = serviceType;
         }
 
+        public FactoryCallSite(ResultCache cache, Type serviceType, object serviceKey, Func<IServiceProvider, object, object> factory) : base(cache)
+        {
+            Factory = sp => factory(sp, serviceKey);
+            ServiceType = serviceType;
+        }
+
         public override Type ServiceType { get; }
         public override Type? ImplementationType => null;
 
index 345dce7..136d3cf 100644 (file)
@@ -8,6 +8,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Reflection.Emit;
+using System.Runtime.InteropServices;
 
 namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 {
@@ -271,10 +272,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
         private static void AddCacheKey(ILEmitResolverBuilderContext argument, ServiceCacheKey key)
         {
-            Debug.Assert(key.Type != null);
+            Debug.Assert(key.ServiceIdentifier != null);
+            var id = key.ServiceIdentifier.Value;
 
-            // new ServiceCacheKey(typeof(key.Type), key.Slot)
-            argument.Generator.Emit(OpCodes.Ldtoken, key.Type);
+            // new ServiceCacheKey(key.ServiceKey, key.type, key.slot)
+            AddConstant(argument, id.ServiceKey);
+            argument.Generator.Emit(OpCodes.Ldtoken, id.ServiceType);
             argument.Generator.Emit(OpCodes.Call, GetTypeFromHandleMethod);
             argument.Generator.Emit(OpCodes.Ldc_I4, key.Slot);
             argument.Generator.Emit(OpCodes.Newobj, CacheKeyCtor);
index 5b4da78..6ba5da3 100644 (file)
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
     {
         public static ResultCache None(Type serviceType)
         {
-            var cacheKey = new ServiceCacheKey(serviceType, 0);
+            var cacheKey = new ServiceCacheKey(ServiceIdentifier.FromServiceType(serviceType), 0);
             return new ResultCache(CallSiteResultCacheLocation.None, cacheKey);
         }
 
@@ -20,9 +20,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
             Key = cacheKey;
         }
 
-        public ResultCache(ServiceLifetime lifetime, Type? type, int slot)
+        public ResultCache(ServiceLifetime lifetime, ServiceIdentifier? serviceIdentifier, int slot)
         {
-            Debug.Assert(lifetime == ServiceLifetime.Transient || type != null);
+            Debug.Assert(lifetime == ServiceLifetime.Transient || serviceIdentifier != null);
 
             switch (lifetime)
             {
@@ -39,7 +39,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                     Location = CallSiteResultCacheLocation.None;
                     break;
             }
-            Key = new ServiceCacheKey(type, slot);
+            Key = new ServiceCacheKey(serviceIdentifier, slot);
         }
 
         public CallSiteResultCacheLocation Location { get; set; }
index 569fbef..1c15746 100644 (file)
@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         /// <summary>
         /// Type of service being cached
         /// </summary>
-        public Type? Type { get; }
+        public ServiceIdentifier? ServiceIdentifier { get; }
 
         /// <summary>
         /// Reverse index of the service when resolved in <c>IEnumerable&lt;Type&gt;</c> where default instance gets slot 0.
@@ -26,9 +26,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         /// </summary>
         public int Slot { get; }
 
-        public ServiceCacheKey(Type? type, int slot)
+        public ServiceCacheKey(object key, Type type, int slot)
         {
-            Type = type;
+            ServiceIdentifier = new ServiceIdentifier(key, type);
+            Slot = slot;
+        }
+
+        public ServiceCacheKey(ServiceIdentifier? type, int slot)
+        {
+            ServiceIdentifier = type;
             Slot = slot;
         }
 
@@ -36,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         /// <param name="other">An instance to compare with this instance.</param>
         /// <returns>true if the current instance is equal to the other instance; otherwise, false.</returns>
         public bool Equals(ServiceCacheKey other) =>
-            Type == other.Type && Slot == other.Slot;
+            ServiceIdentifier.Equals(other.ServiceIdentifier) && Slot == other.Slot;
 
         public override bool Equals([NotNullWhen(true)] object? obj) =>
             obj is ServiceCacheKey other && Equals(other);
@@ -45,7 +51,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         {
             unchecked
             {
-                return ((Type?.GetHashCode() ?? 23) * 397) ^ Slot;
+                return ((ServiceIdentifier?.GetHashCode() ?? 23) * 397) ^ Slot;
             }
         }
     }
index 1ede82e..e3ff3ba 100644 (file)
@@ -20,6 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         public abstract CallSiteKind Kind { get; }
         public ResultCache Cache { get; }
         public object? Value { get; set; }
+        public object? Key { get; set; }
 
         public bool CaptureDisposable =>
             ImplementationType == null ||
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceDescriptorExtensions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceDescriptorExtensions.cs
new file mode 100644 (file)
index 0000000..3345434
--- /dev/null
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
+{
+    internal static class ServiceDescriptorExtensions
+    {
+        public static bool HasImplementationInstance(this ServiceDescriptor serviceDescriptor) => GetImplementationInstance(serviceDescriptor) != null;
+
+        public static bool HasImplementationFactory(this ServiceDescriptor serviceDescriptor) => GetImplementationFactory(serviceDescriptor) != null;
+
+        public static bool HasImplementationType(this ServiceDescriptor serviceDescriptor) => GetImplementationType(serviceDescriptor) != null;
+
+        public static object? GetImplementationInstance(this ServiceDescriptor serviceDescriptor)
+        {
+            return serviceDescriptor.IsKeyedService
+                ? serviceDescriptor.KeyedImplementationInstance
+                : serviceDescriptor.ImplementationInstance;
+        }
+
+        public static object? GetImplementationFactory(this ServiceDescriptor serviceDescriptor)
+        {
+            return serviceDescriptor.IsKeyedService
+                ? serviceDescriptor.KeyedImplementationFactory
+                : serviceDescriptor.ImplementationFactory;
+        }
+
+        [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
+        public static Type? GetImplementationType(this ServiceDescriptor serviceDescriptor)
+        {
+            return serviceDescriptor.IsKeyedService
+                ? serviceDescriptor.KeyedImplementationType
+                : serviceDescriptor.ImplementationType;
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs
new file mode 100644 (file)
index 0000000..8b52653
--- /dev/null
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
+{
+    internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
+    {
+        public object? ServiceKey { get; }
+
+        public Type ServiceType { get; }
+
+        public ServiceIdentifier(Type serviceType)
+        {
+            ServiceType = serviceType;
+        }
+
+        public ServiceIdentifier(object? serviceKey, Type serviceType)
+        {
+            ServiceKey = serviceKey;
+            ServiceType = serviceType;
+        }
+
+        public static ServiceIdentifier FromDescriptor(ServiceDescriptor serviceDescriptor)
+            => new ServiceIdentifier(serviceDescriptor.ServiceKey, serviceDescriptor.ServiceType);
+
+        public static ServiceIdentifier FromServiceType(Type type) => new ServiceIdentifier(null, type);
+
+        public bool Equals(ServiceIdentifier other)
+        {
+            if (ServiceKey == null && other.ServiceKey == null)
+            {
+                return ServiceType.Equals(other.ServiceType);
+            }
+            else if (ServiceKey != null && other.ServiceKey != null)
+            {
+                return ServiceType.Equals(other.ServiceType) && ServiceKey.Equals(other.ServiceKey);
+            }
+            return false;
+        }
+
+        public override bool Equals([NotNullWhen(true)] object? obj)
+        {
+            return obj is ServiceIdentifier && Equals((ServiceIdentifier)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            if (ServiceKey == null)
+            {
+                return ServiceType.GetHashCode();
+            }
+            unchecked
+            {
+                return ((ServiceType?.GetHashCode() ?? 23) * 397) ^ ServiceKey.GetHashCode();
+            }
+        }
+
+        public bool IsConstructedGenericType => ServiceType.IsConstructedGenericType;
+
+        public ServiceIdentifier GetGenericTypeDefinition() => new ServiceIdentifier(ServiceKey, ServiceType.GetGenericTypeDefinition());
+
+        public override string? ToString()
+        {
+            if (ServiceKey == null)
+            {
+                return ServiceType.ToString();
+            }
+
+            return $"({ServiceKey}, {ServiceType})";
+        }
+    }
+}
index ba68712..5ef37ba 100644 (file)
@@ -47,7 +47,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
                 ThrowHelper.ThrowObjectDisposedException();
             }
 
-            return RootProvider.GetService(serviceType, this);
+            return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
         }
 
         public IServiceProvider ServiceProvider => this;
index 2348fbd..613305c 100644 (file)
@@ -17,18 +17,18 @@ namespace Microsoft.Extensions.DependencyInjection
     /// </summary>
     [DebuggerDisplay("{DebuggerToString(),nq}")]
     [DebuggerTypeProxy(typeof(ServiceProviderDebugView))]
-    public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable
+    public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, IDisposable, IAsyncDisposable
     {
         private readonly CallSiteValidator? _callSiteValidator;
 
-        private readonly Func<Type, Func<ServiceProviderEngineScope, object?>> _createServiceAccessor;
+        private readonly Func<ServiceIdentifier, Func<ServiceProviderEngineScope, object?>> _createServiceAccessor;
 
         // Internal for testing
         internal ServiceProviderEngine _engine;
 
         private bool _disposed;
 
-        private readonly ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>> _realizedServices;
+        private readonly ConcurrentDictionary<ServiceIdentifier, Func<ServiceProviderEngineScope, object?>> _realizedServices;
 
         internal CallSiteFactory CallSiteFactory { get; }
 
@@ -50,14 +50,15 @@ namespace Microsoft.Extensions.DependencyInjection
             Root = new ServiceProviderEngineScope(this, isRootScope: true);
             _engine = GetEngine();
             _createServiceAccessor = CreateServiceAccessor;
-            _realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>>();
+            _realizedServices = new ConcurrentDictionary<ServiceIdentifier, Func<ServiceProviderEngineScope, object?>>();
 
             CallSiteFactory = new CallSiteFactory(serviceDescriptors);
             // The list of built in services that aren't part of the list of service descriptors
             // keep this in sync with CallSiteFactory.IsService
-            CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
-            CallSiteFactory.Add(typeof(IServiceScopeFactory), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
-            CallSiteFactory.Add(typeof(IServiceProviderIsService), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
+            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProvider)), new ServiceProviderCallSite());
+            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceScopeFactory)), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
+            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProviderIsService)), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
+            CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProviderIsKeyedService)), new ConstantCallSite(typeof(IServiceProviderIsKeyedService), CallSiteFactory));
 
             if (options.ValidateScopes)
             {
@@ -94,7 +95,20 @@ namespace Microsoft.Extensions.DependencyInjection
         /// </summary>
         /// <param name="serviceType">The type of the service to get.</param>
         /// <returns>The service that was produced.</returns>
-        public object? GetService(Type serviceType) => GetService(serviceType, Root);
+        public object? GetService(Type serviceType) => GetService(ServiceIdentifier.FromServiceType(serviceType), Root);
+
+        public object? GetKeyedService(Type serviceType, object? serviceKey)
+            => GetService(new ServiceIdentifier(serviceKey, serviceType), Root);
+
+        public object GetRequiredKeyedService(Type serviceType, object? serviceKey)
+        {
+            object? service = GetKeyedService(serviceType, serviceKey);
+            if (service == null)
+            {
+                throw new InvalidOperationException(SR.Format(SR.NoServiceRegistered, serviceType));
+            }
+            return service;
+        }
 
         internal bool IsDisposed() => _disposed;
 
@@ -128,16 +142,16 @@ namespace Microsoft.Extensions.DependencyInjection
             _callSiteValidator?.ValidateResolution(callSite, scope, Root);
         }
 
-        internal object? GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
+        internal object? GetService(ServiceIdentifier serviceIdentifier, ServiceProviderEngineScope serviceProviderEngineScope)
         {
             if (_disposed)
             {
                 ThrowHelper.ThrowObjectDisposedException();
             }
 
-            Func<ServiceProviderEngineScope, object?> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
+            Func<ServiceProviderEngineScope, object?> realizedService = _realizedServices.GetOrAdd(serviceIdentifier, _createServiceAccessor);
             var result = realizedService.Invoke(serviceProviderEngineScope);
-            System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
+            System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceIdentifier));
             return result;
         }
 
@@ -162,12 +176,12 @@ namespace Microsoft.Extensions.DependencyInjection
             }
         }
 
-        private Func<ServiceProviderEngineScope, object?> CreateServiceAccessor(Type serviceType)
+        private Func<ServiceProviderEngineScope, object?> CreateServiceAccessor(ServiceIdentifier serviceIdentifier)
         {
-            ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
+            ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceIdentifier, new CallSiteChain());
             if (callSite != null)
             {
-                DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceType, callSite);
+                DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceIdentifier.ServiceType, callSite);
                 OnCreate(callSite);
 
                 // Optimize singleton case
@@ -176,7 +190,7 @@ namespace Microsoft.Extensions.DependencyInjection
                     object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
                     return scope =>
                     {
-                        DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
+                        DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
                         return value;
                     };
                 }
@@ -185,7 +199,7 @@ namespace Microsoft.Extensions.DependencyInjection
                 return scope =>
                 {
                     OnResolve(callSite, scope);
-                    DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
+                    DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
                     return realizedService(scope);
                 };
             }
@@ -195,7 +209,7 @@ namespace Microsoft.Extensions.DependencyInjection
 
         internal void ReplaceServiceAccessor(ServiceCallSite callSite, Func<ServiceProviderEngineScope, object?> accessor)
         {
-            _realizedServices[callSite.ServiceType] = accessor;
+            _realizedServices[new ServiceIdentifier(callSite.Key, callSite.ServiceType)] = accessor;
         }
 
         internal IServiceScope CreateScope()
index fa72121..26b410e 100644 (file)
@@ -11,6 +11,14 @@ using Xunit;
 
 namespace Microsoft.Extensions.DependencyInjection.Tests
 {
+    internal static class CallSiteTestsExtensions
+    {
+        internal static ServiceCallSite GetCallSite(this CallSiteFactory callSiteFactory, Type type, CallSiteChain callSiteChain)
+        {
+            return callSiteFactory.GetCallSite(ServiceIdentifier.FromServiceType(type), callSiteChain);
+        }
+    }
+
     public class CallSiteTests
     {
         public static IEnumerable<object[]> TestServiceDescriptors(ServiceLifetime lifetime)
@@ -136,7 +144,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
 
             var disposables = new List<object>();
             var provider = new ServiceProvider(descriptors, ServiceProviderOptions.Default);
-            
+
             var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
             var compiledCallSite = CompileCallSite(callSite, provider);
 
@@ -185,7 +193,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
 
             var disposables = new List<object>();
             var provider = new ServiceProvider(descriptors, ServiceProviderOptions.Default);
-            
+
             var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
             var compiledCallSite = CompileCallSite(callSite, provider);
 
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/KeyedServiceProviderContainerTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/KeyedServiceProviderContainerTests.cs
new file mode 100644 (file)
index 0000000..79322bb
--- /dev/null
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection.Specification;
+using Xunit;
+
+namespace Microsoft.Extensions.DependencyInjection.Tests
+{
+    public class KeyedServiceProviderDefaultContainerTests : KeyedDependencyInjectionSpecificationTests
+    {
+        protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.Default);
+    }
+
+    public class KeyedServiceProviderDynamicContainerTests : KeyedDependencyInjectionSpecificationTests
+    {
+        protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider();
+    }
+
+    public class KeyedServiceProviderExpressionContainerTests : KeyedDependencyInjectionSpecificationTests
+    {
+        protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.Expressions);
+    }
+
+    public class KeyedServiceProviderILEmitContainerTests : KeyedDependencyInjectionSpecificationTests
+    {
+        protected override IServiceProvider CreateServiceProvider(IServiceCollection collection) => collection.BuildServiceProvider(ServiceProviderMode.ILEmit);
+    }
+}
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceCollectionKeyedServiceExtensionsTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceCollectionKeyedServiceExtensionsTest.cs
new file mode 100644 (file)
index 0000000..25b6106
--- /dev/null
@@ -0,0 +1,496 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.DependencyInjection.Specification.Fakes;
+using Microsoft.Extensions.DependencyInjection.Tests;
+using Xunit;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public class ServiceCollectionKeyedServiceExtensionsTest
+    {
+        private static readonly FakeService _instance = new FakeService();
+
+        public static TheoryData AddImplementationTypeData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                var implementationType = typeof(FakeService);
+                return new TheoryData<Action<IServiceCollection>, Type, object, Type, ServiceLifetime>
+                {
+                    { collection => collection.AddKeyedTransient(serviceType, "some-key-1", implementationType), serviceType, "some-key-1", implementationType, ServiceLifetime.Transient },
+                    { collection => collection.AddKeyedTransient<IFakeService, FakeService>("some-key-2"), serviceType, "some-key-2", implementationType, ServiceLifetime.Transient },
+                    { collection => collection.AddKeyedTransient<IFakeService>("some-key-3"), serviceType, "some-key-3", serviceType, ServiceLifetime.Transient },
+                    { collection => collection.AddKeyedTransient(implementationType, "some-key-4"), implementationType, "some-key-4", implementationType, ServiceLifetime.Transient },
+
+                    { collection => collection.AddKeyedScoped(serviceType, "some-key-5", implementationType), serviceType, "some-key-5", implementationType, ServiceLifetime.Scoped },
+                    { collection => collection.AddKeyedScoped<IFakeService, FakeService>("some-key-6"), serviceType, "some-key-6", implementationType, ServiceLifetime.Scoped },
+                    { collection => collection.AddKeyedScoped<IFakeService>("some-key-7"), serviceType, "some-key-7", serviceType, ServiceLifetime.Scoped },
+                    { collection => collection.AddKeyedScoped(implementationType, "some-key-8"), implementationType, "some-key-8", implementationType, ServiceLifetime.Scoped },
+
+                    { collection => collection.AddKeyedSingleton(serviceType, "some-key-9", implementationType), serviceType, "some-key-9", implementationType, ServiceLifetime.Singleton },
+                    { collection => collection.AddKeyedSingleton<IFakeService, FakeService>("some-key-10"), serviceType, "some-key-10", implementationType, ServiceLifetime.Singleton },
+                    { collection => collection.AddKeyedSingleton<IFakeService>("some-key-12"), serviceType, "some-key-12", serviceType, ServiceLifetime.Singleton },
+                    { collection => collection.AddKeyedSingleton(serviceType: implementationType, "some-key-13"), implementationType, "some-key-13", implementationType, ServiceLifetime.Singleton },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(AddImplementationTypeData))]
+        public void AddWithTypeAddsServiceWithRightLifecyle(Action<IServiceCollection> addTypeAction,
+                                                            Type expectedServiceType,
+                                                            object expectedKey,
+                                                            Type expectedImplementationType,
+                                                            ServiceLifetime lifeCycle)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            // Act
+            addTypeAction(collection);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(expectedServiceType, descriptor.ServiceType);
+            Assert.Equal(expectedKey, descriptor.ServiceKey);
+            Assert.Equal(expectedImplementationType, descriptor.KeyedImplementationType);
+            Assert.Equal(lifeCycle, descriptor.Lifetime);
+        }
+
+        public static TheoryData AddImplementationFactoryData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                var implementationType = typeof(FakeService);
+                var objectType = typeof(object);
+
+                return new TheoryData<Action<IServiceCollection>, Type, object, Type, ServiceLifetime>
+                {
+                    { collection => collection.AddKeyedTransient(serviceType, "some-key-1", (s,k) => new FakeService()), serviceType, "some-key-1", objectType, ServiceLifetime.Transient },
+                    { collection => collection.AddKeyedTransient<IFakeService>("some-key-2", (s,k) => new FakeService()), serviceType, "some-key-2", serviceType, ServiceLifetime.Transient },
+                    { collection => collection.AddKeyedTransient<IFakeService, FakeService>("some-key-3", (s,k) => new FakeService()), serviceType, "some-key-3", implementationType, ServiceLifetime.Transient },
+
+                    { collection => collection.AddKeyedScoped(serviceType, "some-key-4", (s,k) => new FakeService()), serviceType, "some-key-4", objectType, ServiceLifetime.Scoped },
+                    { collection => collection.AddKeyedScoped<IFakeService>("some-key-5", (s,k) => new FakeService()), serviceType, "some-key-5", serviceType, ServiceLifetime.Scoped },
+                    { collection => collection.AddKeyedScoped<IFakeService, FakeService>("some-key-6", (s,k) => new FakeService()), serviceType, "some-key-6", implementationType, ServiceLifetime.Scoped },
+
+                    { collection => collection.AddKeyedSingleton(serviceType, "some-key-7", (s,k) => new FakeService()), serviceType, "some-key-7", objectType, ServiceLifetime.Singleton },
+                    { collection => collection.AddKeyedSingleton<IFakeService>("some-key-8", (s,k) => new FakeService()), serviceType, "some-key-8", serviceType, ServiceLifetime.Singleton },
+                    { collection => collection.AddKeyedSingleton<IFakeService, FakeService>("some-key-9", (s,k) => new FakeService()), serviceType, "some-key-9", implementationType, ServiceLifetime.Singleton },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(AddImplementationFactoryData))]
+        public void AddWithFactoryAddsServiceWithRightLifecyle(
+            Action<IServiceCollection> addAction,
+            Type serviceType,
+            object? serviceKey,
+            Type implementationType,
+            ServiceLifetime lifeCycle)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            // Act
+            addAction(collection);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(serviceType, descriptor.ServiceType);
+            Assert.Equal(serviceKey, descriptor.ServiceKey);
+            Assert.Equal(implementationType, descriptor.GetImplementationType());
+            Assert.Equal(lifeCycle, descriptor.Lifetime);
+        }
+
+        public static TheoryData AddSingletonData
+        {
+            get
+            {
+                return new TheoryData<Action<IServiceCollection>>
+                {
+                    { collection => collection.AddKeyedSingleton<IFakeService>("service", _instance) },
+                    { collection => collection.AddKeyedSingleton(typeof(IFakeService), "service", _instance) },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(AddSingletonData))]
+        public void AddSingleton_AddsWithSingletonLifecycle(Action<IServiceCollection> addAction)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            // Act
+            addAction(collection);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(typeof(IFakeService), descriptor.ServiceType);
+            Assert.Equal("service", descriptor.ServiceKey.ToString());
+            Assert.Same(_instance, descriptor.KeyedImplementationInstance);
+            Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
+        }
+
+        [Theory]
+        [MemberData(nameof(AddSingletonData))]
+        public void TryAddNoOpFailsIfExists(Action<IServiceCollection> addAction)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            addAction(collection);
+            var d = ServiceDescriptor.KeyedTransient<IFakeService, FakeService>("service");
+
+            // Act
+            collection.TryAdd(d);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(typeof(IFakeService), descriptor.ServiceType);
+            Assert.Same(_instance, descriptor.KeyedImplementationInstance);
+            Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
+        }
+
+        public static TheoryData TryAddImplementationTypeData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                var implementationType = typeof(FakeService);
+                return new TheoryData<Action<IServiceCollection>, Type, object, Type, ServiceLifetime>
+                {
+                    { collection => collection.TryAddKeyedTransient(serviceType, "key-1", implementationType), serviceType, "key-1", implementationType, ServiceLifetime.Transient },
+                    { collection => collection.TryAddKeyedTransient<IFakeService, FakeService>("key-2"), serviceType, "key-2", implementationType, ServiceLifetime.Transient },
+                    { collection => collection.TryAddKeyedTransient<IFakeService>("key-3"), serviceType, "key-3", serviceType, ServiceLifetime.Transient },
+                    { collection => collection.TryAddKeyedTransient(implementationType, "key-4"), implementationType, "key-4", implementationType, ServiceLifetime.Transient },
+
+                    { collection => collection.TryAddKeyedScoped(serviceType, "key-1", implementationType), serviceType, "key-1", implementationType, ServiceLifetime.Scoped },
+                    { collection => collection.TryAddKeyedScoped<IFakeService, FakeService>("key-2"), serviceType, "key-2", implementationType, ServiceLifetime.Scoped },
+                    { collection => collection.TryAddKeyedScoped<IFakeService>("key-3"), serviceType, "key-3", serviceType, ServiceLifetime.Scoped },
+                    { collection => collection.TryAddKeyedScoped(implementationType, "key-4"), implementationType, "key-4", implementationType, ServiceLifetime.Scoped },
+
+                    { collection => collection.TryAddKeyedSingleton(serviceType, "key-5", implementationType), serviceType, "key-5", implementationType, ServiceLifetime.Singleton },
+                    { collection => collection.TryAddKeyedSingleton<IFakeService, FakeService>("key-6"), serviceType, "key-6", implementationType, ServiceLifetime.Singleton },
+                    { collection => collection.TryAddKeyedSingleton<IFakeService>("key-7"), serviceType, "key-7", serviceType, ServiceLifetime.Singleton },
+                    { collection => collection.TryAddKeyedSingleton(service: implementationType, "key-8"), implementationType, "key-8", implementationType, ServiceLifetime.Singleton },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(TryAddImplementationTypeData))]
+        public void TryAdd_WithType_AddsService(
+            Action<IServiceCollection> addAction,
+            Type expectedServiceType,
+            object expectedServiceKey,
+            Type expectedImplementationType,
+            ServiceLifetime expectedLifetime)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            // Act
+            addAction(collection);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(expectedServiceType, descriptor.ServiceType);
+            Assert.Equal(expectedServiceKey, descriptor.ServiceKey);
+            Assert.Same(expectedImplementationType, descriptor.KeyedImplementationType);
+            Assert.Equal(expectedLifetime, descriptor.Lifetime);
+        }
+
+        [Theory]
+        [MemberData(nameof(TryAddImplementationTypeData))]
+        public void TryAdd_WithType_DoesNotAddDuplicate(
+            Action<IServiceCollection> addAction,
+            Type expectedServiceType,
+            object expectedServiceKey,
+            // Test verifies that descriptor is not added so we don't need to assert it's properties
+#pragma warning disable xUnit1026
+            Type expectedImplementationType,
+            ServiceLifetime expectedLifetime
+#pragma warning restore xUnit1026
+            )
+        {
+            // Arrange
+            var collection = new ServiceCollection
+            {
+                ServiceDescriptor.KeyedTransient(expectedServiceType, expectedServiceKey, expectedServiceType)
+            };
+
+            // Act
+            addAction(collection);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(expectedServiceType, descriptor.ServiceType);
+            Assert.Same(expectedServiceType, descriptor.KeyedImplementationType);
+            Assert.Equal(ServiceLifetime.Transient, descriptor.Lifetime);
+        }
+
+        [Fact]
+        public void TryAddIfMissingActuallyAdds()
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            var key = new object();
+            var d = ServiceDescriptor.KeyedTransient<IFakeService, FakeService>(key);
+
+            // Act
+            collection.TryAdd(d);
+
+            // Assert
+            var descriptor = Assert.Single(collection);
+            Assert.Equal(typeof(IFakeService), descriptor.ServiceType);
+            Assert.Equal(key, descriptor.ServiceKey);
+            Assert.Null(descriptor.KeyedImplementationInstance);
+            Assert.Equal(ServiceLifetime.Transient, descriptor.Lifetime);
+        }
+
+        public static TheoryData TryAddEnumerableImplementationTypeData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                var implementationType = typeof(FakeService);
+                return new TheoryData<ServiceDescriptor, Type, object, Type, ServiceLifetime>
+                {
+                    { ServiceDescriptor.KeyedTransient<IFakeService, FakeService>("service1"), serviceType, "service1", implementationType, ServiceLifetime.Transient },
+                    { ServiceDescriptor.KeyedTransient<IFakeService, FakeService>("service2", (s,k) => new FakeService()), serviceType, "service2", implementationType, ServiceLifetime.Transient },
+
+                    { ServiceDescriptor.KeyedScoped<IFakeService, FakeService>("service3"), serviceType, "service3", implementationType, ServiceLifetime.Scoped },
+                    { ServiceDescriptor.KeyedScoped<IFakeService, FakeService>("service4", (s,k) => new FakeService()), serviceType, "service4", implementationType, ServiceLifetime.Scoped },
+
+                    { ServiceDescriptor.KeyedSingleton<IFakeService, FakeService>("service5"), serviceType, "service5", implementationType, ServiceLifetime.Singleton },
+                    { ServiceDescriptor.KeyedSingleton<IFakeService, FakeService>("service6", (s,k) => new FakeService()), serviceType, "service6", implementationType, ServiceLifetime.Singleton },
+
+                    { ServiceDescriptor.KeyedSingleton<IFakeService>("service6", _instance), serviceType, "service6", implementationType, ServiceLifetime.Singleton },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(TryAddEnumerableImplementationTypeData))]
+        public void TryAddEnumerable_AddsService(
+            ServiceDescriptor descriptor,
+            Type expectedServiceType,
+            object expectedKey,
+            Type expectedImplementationType,
+            ServiceLifetime expectedLifetime)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            // Act
+            collection.TryAddEnumerable(descriptor);
+
+            // Assert
+            var d = Assert.Single(collection);
+            Assert.Equal(expectedServiceType, d.ServiceType);
+            Assert.Equal(expectedKey, d.ServiceKey);
+            Assert.Equal(expectedImplementationType, d.GetImplementationType());
+            Assert.Equal(expectedLifetime, d.Lifetime);
+        }
+
+
+        [Theory]
+        [MemberData(nameof(TryAddEnumerableImplementationTypeData))]
+        public void TryAddEnumerable_DoesNotAddDuplicate(
+            ServiceDescriptor descriptor,
+            Type expectedServiceType,
+            object expectedKey,
+            Type expectedImplementationType,
+            ServiceLifetime expectedLifetime)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            collection.TryAddEnumerable(descriptor);
+
+            // Act
+            collection.TryAddEnumerable(descriptor);
+
+            // Assert
+            var d = Assert.Single(collection);
+            Assert.Equal(expectedServiceType, d.ServiceType);
+            Assert.Equal(expectedKey, d.ServiceKey);
+            Assert.Equal(expectedImplementationType, d.GetImplementationType());
+            Assert.Equal(expectedLifetime, d.Lifetime);
+        }
+
+        public static TheoryData TryAddEnumerableInvalidImplementationTypeData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                var key = new object();
+                var implementationType = typeof(FakeService);
+                var objectType = typeof(object);
+
+                return new TheoryData<ServiceDescriptor, Type, Type>
+                {
+                    { ServiceDescriptor.KeyedTransient<IFakeService>(key, (s,k) => new FakeService()), serviceType, serviceType },
+                    { ServiceDescriptor.KeyedTransient(serviceType, key, (s,k) => new FakeService()), serviceType, objectType },
+
+                    { ServiceDescriptor.KeyedScoped<IFakeService>(key, (s,k) => new FakeService()), serviceType, serviceType },
+                    { ServiceDescriptor.KeyedScoped(serviceType, key, (s,k) => new FakeService()), serviceType, objectType },
+
+                    { ServiceDescriptor.KeyedSingleton<IFakeService>(key, (s,k) => new FakeService()), serviceType, serviceType },
+                    { ServiceDescriptor.KeyedSingleton(serviceType, key, (s,k) => new FakeService()), serviceType, objectType },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(TryAddEnumerableInvalidImplementationTypeData))]
+        public void TryAddEnumerable_ThrowsWhenAddingIndistinguishableImplementationType(
+            ServiceDescriptor descriptor,
+            Type serviceType,
+            Type implementationType)
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+
+            AssertExtensions.ThrowsContains<ArgumentException>(() => collection.TryAddEnumerable(descriptor), 
+                string.Format(@"Implementation type cannot be '{0}' because it is indistinguishable from other services registered for '{1}'.", implementationType, serviceType));
+        }
+
+        [Fact]
+        public void AddSequence_AddsServicesToCollection()
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            var descriptor1 = new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient);
+            var descriptor2 = new ServiceDescriptor(typeof(IFakeOuterService), "key1", typeof(FakeOuterService), ServiceLifetime.Transient);
+            var descriptors = new[] { descriptor1, descriptor2 };
+
+            // Act
+            var result = collection.Add(descriptors);
+
+            // Assert
+            Assert.Equal(descriptors, collection);
+        }
+
+        [Fact]
+        public void Replace_AddsServiceIfServiceTypeIsNotRegistered()
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            var descriptor1 = new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient);
+            var descriptor2 = new ServiceDescriptor(typeof(IFakeOuterService), "key1", typeof(FakeOuterService), ServiceLifetime.Transient);
+            collection.Add(descriptor1);
+
+            // Act
+            collection.Replace(descriptor2);
+
+            // Assert
+            Assert.Equal(new[] { descriptor1, descriptor2 }, collection);
+        }
+
+        [Fact]
+        public void Replace_ReplacesFirstServiceWithMatchingServiceType()
+        {
+            // Arrange
+            var collection = new ServiceCollection();
+            var descriptor1 = new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient);
+            var descriptor2 = new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient);
+            collection.Add(descriptor1);
+            collection.Add(descriptor2);
+            var descriptor3 = new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Singleton);
+
+            // Act
+            collection.Replace(descriptor3);
+
+            // Assert
+            Assert.Equal(new[] { descriptor2, descriptor3 }, collection);
+        }
+
+        [Fact]
+        public void RemoveAll_RemovesAllServicesWithMatchingServiceType()
+        {
+            // Arrange
+            var descriptor = new ServiceDescriptor(typeof(IFakeServiceInstance), "key1", typeof(FakeService), ServiceLifetime.Transient);
+            var collection = new ServiceCollection
+            {
+                descriptor,
+                new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient),
+                new ServiceDescriptor(typeof(IFakeService), "key1", typeof(FakeService), ServiceLifetime.Transient)
+            };
+
+            // Act
+            collection.RemoveAllKeyed<IFakeService>("key1");
+
+            // Assert
+            Assert.Equal(new[] { descriptor }, collection);
+        }
+
+        public static TheoryData NullServiceKeyData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                object key = null;
+                var implementationType = typeof(FakeService);
+                var objectType = typeof(object);
+
+                return new TheoryData<ServiceDescriptor>
+                {
+                    { ServiceDescriptor.KeyedTransient<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedTransient<IFakeService>(key, (sp, key) => new FakeService()) },
+                    { ServiceDescriptor.KeyedScoped<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedScoped<IFakeService>(key, (sp, key) => new FakeService()) },
+                    { ServiceDescriptor.KeyedSingleton<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedSingleton<IFakeService>(key, new FakeService()) },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(NullServiceKeyData))]
+        public void NullServiceKey_IsKeyedServiceFalse(ServiceDescriptor serviceDescriptor)
+        {
+            Assert.False(serviceDescriptor.IsKeyedService);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.KeyedImplementationInstance);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.KeyedImplementationType);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.KeyedImplementationFactory);
+        }
+
+        public static TheoryData NotNullServiceKeyData
+        {
+            get
+            {
+                var serviceType = typeof(IFakeService);
+                object key = new();
+                var implementationType = typeof(FakeService);
+                var objectType = typeof(object);
+
+                return new TheoryData<ServiceDescriptor>
+                {
+                    { ServiceDescriptor.KeyedTransient<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedTransient<IFakeService>(key, (sp, key) => new FakeService()) },
+                    { ServiceDescriptor.KeyedScoped<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedScoped<IFakeService>(key, (sp, key) => new FakeService()) },
+                    { ServiceDescriptor.KeyedSingleton<IFakeService, FakeService>(key) },
+                    { ServiceDescriptor.KeyedSingleton<IFakeService>(key, new FakeService()) },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(NotNullServiceKeyData))]
+        public void NotNullServiceKey_IsKeyedServiceTrue(ServiceDescriptor serviceDescriptor)
+        {
+            Assert.True(serviceDescriptor.IsKeyedService);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.ImplementationInstance);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.ImplementationType);
+            Assert.Throws<InvalidOperationException>(() => serviceDescriptor.ImplementationFactory);
+        }
+    }
+}
index dbf4e7b..ade998a 100644 (file)
@@ -789,7 +789,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
             Assert.Equal(expectedLocation, callSite.Cache.Location);
             Assert.Equal(0, callSite.Cache.Key.Slot);
-            Assert.Equal(typeof(IEnumerable<FakeService>), callSite.Cache.Key.Type);
+            Assert.Equal(typeof(IEnumerable<FakeService>), callSite.Cache.Key.ServiceIdentifier.Value.ServiceType);
         }
 
         [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
@@ -1001,7 +1001,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
 
             var callSiteFactory = new CallSiteFactory(collection.ToArray());
 
-            return type => callSiteFactory.GetCallSite(type, new CallSiteChain());
+            return type => callSiteFactory.GetCallSite(ServiceIdentifier.FromServiceType(type), new CallSiteChain());
         }
 
         private static IEnumerable<Type> GetParameters(ConstructorCallSite constructorCallSite) =>
index e801497..f3de857 100644 (file)
@@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
         {
             var provider = new ServiceProvider(new ServiceCollection(), ServiceProviderOptions.Default);
             var serviceProviderEngineScope = new ServiceProviderEngineScope(provider, isRootScope: true);
-            serviceProviderEngineScope.ResolvedServices.Add(new ServiceCacheKey(typeof(IFakeService), 0), null);
+            serviceProviderEngineScope.ResolvedServices.Add(new ServiceCacheKey(ServiceIdentifier.FromServiceType(typeof(IFakeService)), 0), null);
             serviceProviderEngineScope.Dispose();
             serviceProviderEngineScope.Dispose();
         }