Large diffs are not rendered by default.

Expand Up @@ -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
Expand Down

Large diffs are not rendered by default.

@@ -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
steveharter marked this conversation as resolved.
Show resolved Hide resolved
{
}
}
@@ -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);
}
}
}
@@ -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();
steveharter marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}
}
@@ -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));
}
}
}
Expand Up @@ -19,11 +19,13 @@ public static partial class ServiceCollectionContainerBuilderExtensions
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
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -243,20 +244,27 @@ private static void AppendServiceDescriptor(StringBuilder builder, ServiceDescri
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
Expand Down
Expand Up @@ -186,4 +186,10 @@
<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>
Expand Up @@ -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(')');
Expand All @@ -70,7 +70,7 @@ private void AppendResolutionPath(StringBuilder builder, Type currentlyResolving
builder.Append(" -> ");
}

builder.Append(TypeNameHelper.GetTypeDisplayName(currentlyResolving));
builder.Append(TypeNameHelper.GetTypeDisplayName(currentlyResolving.ServiceType));
}

private readonly struct ChainItemInfo
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -7,7 +7,7 @@

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>();
Expand All @@ -30,13 +30,13 @@ public void ValidateResolution(ServiceCallSite callSite, IServiceScope scope, IS
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()));
}
Expand Down
Expand Up @@ -15,6 +15,12 @@ public FactoryCallSite(ResultCache cache, Type serviceType, Func<IServiceProvide
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;

Expand Down
Expand Up @@ -8,6 +8,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
Expand Down Expand Up @@ -271,10 +272,12 @@ private static void AddConstant(ILEmitResolverBuilderContext argument, object? v

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);
Expand Down
Expand Up @@ -10,7 +10,7 @@ internal struct ResultCache
{
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);
}

Expand All @@ -20,9 +20,9 @@ internal ResultCache(CallSiteResultCacheLocation lifetime, ServiceCacheKey cache
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)
{
Expand All @@ -39,7 +39,7 @@ public ResultCache(ServiceLifetime lifetime, Type? type, int slot)
Location = CallSiteResultCacheLocation.None;
break;
}
Key = new ServiceCacheKey(type, slot);
Key = new ServiceCacheKey(serviceIdentifier, slot);
}

public CallSiteResultCacheLocation Location { get; set; }
Expand Down
Expand Up @@ -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.
Expand All @@ -26,17 +26,23 @@ 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;
}

/// <summary>Indicates whether the current instance is equal to another instance of the same type.</summary>
/// <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);
Expand All @@ -45,7 +51,7 @@ public override int GetHashCode()
{
unchecked
{
return ((Type?.GetHashCode() ?? 23) * 397) ^ Slot;
return ((ServiceIdentifier?.GetHashCode() ?? 23) * 397) ^ Slot;
}
}
}
Expand Down
Expand Up @@ -20,6 +20,7 @@ protected ServiceCallSite(ResultCache cache)
public abstract CallSiteKind Kind { get; }
public ResultCache Cache { get; }
public object? Value { get; set; }
public object? Key { get; set; }

public bool CaptureDisposable =>
ImplementationType == null ||
Expand Down
@@ -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;
}
}
}
@@ -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})";
}
}
}
Expand Up @@ -47,7 +47,7 @@ public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
ThrowHelper.ThrowObjectDisposedException();
}

return RootProvider.GetService(serviceType, this);
return RootProvider.GetService(ServiceIdentifier.FromServiceType(serviceType), this);
}

public IServiceProvider ServiceProvider => this;
Expand Down
Expand Up @@ -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; }

Expand All @@ -50,14 +50,15 @@ internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, Serv
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));
benjaminpetit marked this conversation as resolved.
Show resolved Hide resolved
CallSiteFactory.Add(ServiceIdentifier.FromServiceType(typeof(IServiceProviderIsKeyedService)), new ConstantCallSite(typeof(IServiceProviderIsKeyedService), CallSiteFactory));

if (options.ValidateScopes)
{
Expand Down Expand Up @@ -94,7 +95,20 @@ internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, Serv
/// </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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there are no null checks on the object serviceKey and calling GetKeyedService(serviceType, null) is equivalent to calling GetService(serviceType)?

I actually like that behavior, but then we should mark the serviceKey as nullable everywhere in the public APIs. If we don't like that behavior, we can throw an ArgumentNullException. Either way, we should test the behaviour.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but the only issue is with registration with a factory method, where the key is mandatory (since the factory takes the key in parameter).


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;

Expand Down Expand Up @@ -128,16 +142,16 @@ private void OnResolve(ServiceCallSite callSite, IServiceScope scope)
_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;
}

Expand All @@ -162,12 +176,12 @@ private void ValidateService(ServiceDescriptor descriptor)
}
}

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
Expand All @@ -176,7 +190,7 @@ private void ValidateService(ServiceDescriptor descriptor)
object? value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return scope =>
{
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
return value;
};
}
Expand All @@ -185,7 +199,7 @@ private void ValidateService(ServiceDescriptor descriptor)
return scope =>
{
OnResolve(callSite, scope);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceIdentifier.ServiceType);
return realizedService(scope);
};
}
Expand All @@ -195,7 +209,7 @@ private void ValidateService(ServiceDescriptor descriptor)

internal void ReplaceServiceAccessor(ServiceCallSite callSite, Func<ServiceProviderEngineScope, object?> accessor)
{
_realizedServices[callSite.ServiceType] = accessor;
_realizedServices[new ServiceIdentifier(callSite.Key, callSite.ServiceType)] = accessor;
}

internal IServiceScope CreateScope()
Expand Down
Expand Up @@ -11,6 +11,14 @@

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)
Expand Down Expand Up @@ -136,7 +144,7 @@ public void BuildExpressionAddsDisposableCaptureForDisposableServices(ServiceLif

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);

Expand Down Expand Up @@ -185,7 +193,7 @@ public void BuildExpressionElidesDisposableCaptureForNonDisposableServices(Servi

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);

Expand Down
@@ -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);
}
}

Large diffs are not rendered by default.

Expand Up @@ -789,7 +789,7 @@ public void CreateCallSite_EnumberableCachedAtLowestLevel(ServiceDescriptor[] de

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))]
Expand Down Expand Up @@ -1001,7 +1001,7 @@ public void VerifyDynamicCodeNotSupportedChecks()

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) =>
Expand Down
Expand Up @@ -14,7 +14,7 @@ public void DoubleDisposeWorks()
{
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();
}
Expand Down