diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs index c220632c3bdf18..c46fe33c75f1d3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using Microsoft.Extensions.DependencyInjection.Specification.Fakes; @@ -286,6 +287,280 @@ public void ResolveKeyedServicesAnyKeyConsistencyWithAnyKeyRegistration() Assert.Throws(() => provider2.GetKeyedService(KeyedService.AnyKey)); } + [Theory] + [InlineData(true)] + [InlineData(false)] + // Test ordering and slot assignments when DI calls the service's constructor + // across keyed services with different service types and keys. + public void ResolveWithAnyKeyQuery_Constructor(bool anyKeyQueryBeforeSingletonQueries) + { + var serviceCollection = new ServiceCollection(); + + // Interweave these to check that the slot \ ordering logic is correct. + // Each unique key + its service Type maintains their own slot in a AnyKey query. + serviceCollection.AddKeyedSingleton("key1"); + serviceCollection.AddKeyedSingleton("key1"); + serviceCollection.AddKeyedSingleton("key2"); + serviceCollection.AddKeyedSingleton("key2"); + serviceCollection.AddKeyedSingleton("key3"); + serviceCollection.AddKeyedSingleton("key3"); + + var provider = CreateServiceProvider(serviceCollection); + + TestServiceA[] allInstancesA = null; + TestServiceB[] allInstancesB = null; + + if (anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + var serviceA1 = provider.GetKeyedService("key1"); + var serviceB1 = provider.GetKeyedService("key1"); + var serviceA2 = provider.GetKeyedService("key2"); + var serviceB2 = provider.GetKeyedService("key2"); + var serviceA3 = provider.GetKeyedService("key3"); + var serviceB3 = provider.GetKeyedService("key3"); + + if (!anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + Assert.Equal( + new[] { serviceA1, serviceA2, serviceA3 }, + allInstancesA); + + Assert.Equal( + new[] { serviceB1, serviceB2, serviceB3 }, + allInstancesB); + + void DoAnyKeyQuery() + { + IEnumerable allA = provider.GetKeyedServices(KeyedService.AnyKey); + IEnumerable allB = provider.GetKeyedServices(KeyedService.AnyKey); + + // Verify caching returns the same IEnumerable<> instance. + Assert.Same(allA, provider.GetKeyedServices(KeyedService.AnyKey)); + Assert.Same(allB, provider.GetKeyedServices(KeyedService.AnyKey)); + + allInstancesA = allA.ToArray(); + allInstancesB = allB.ToArray(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + // Test ordering and slot assignments when DI calls the service's constructor + // across keyed services with different service types with duplicate keys. + public void ResolveWithAnyKeyQuery_Constructor_Duplicates(bool anyKeyQueryBeforeSingletonQueries) + { + var serviceCollection = new ServiceCollection(); + + // Interweave these to check that the slot \ ordering logic is correct. + // Each unique key + its service Type maintains their own slot in a AnyKey query. + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddKeyedSingleton("key"); + serviceCollection.AddKeyedSingleton("key"); + + var provider = CreateServiceProvider(serviceCollection); + + TestServiceA[] allInstancesA = null; + TestServiceB[] allInstancesB = null; + + if (anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + var serviceA = provider.GetKeyedService("key"); + Assert.Same(serviceA, provider.GetKeyedService("key")); + + var serviceB = provider.GetKeyedService("key"); + Assert.Same(serviceB, provider.GetKeyedService("key")); + + if (!anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + // An AnyKey query we get back the last registered service for duplicates. + // The first and second services are effectively hidden unless we query all. + Assert.Equal(3, allInstancesA.Length); + Assert.Same(serviceA, allInstancesA[2]); + Assert.NotSame(serviceA, allInstancesA[1]); + Assert.NotSame(serviceA, allInstancesA[0]); + Assert.NotSame(allInstancesA[0], allInstancesA[1]); + + Assert.Equal(3, allInstancesB.Length); + Assert.Same(serviceB, allInstancesB[2]); + Assert.NotSame(serviceB, allInstancesB[1]); + Assert.NotSame(serviceB, allInstancesB[0]); + Assert.NotSame(allInstancesB[0], allInstancesB[1]); + + void DoAnyKeyQuery() + { + IEnumerable allA = provider.GetKeyedServices(KeyedService.AnyKey); + IEnumerable allB = provider.GetKeyedServices(KeyedService.AnyKey); + + // Verify caching returns the same IEnumerable<> instances. + Assert.Same(allA, provider.GetKeyedServices(KeyedService.AnyKey)); + Assert.Same(allB, provider.GetKeyedServices(KeyedService.AnyKey)); + + allInstancesA = allA.ToArray(); + allInstancesB = allB.ToArray(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + // Test ordering and slot assignments when service is provided + // across keyed services with different service types and keys. + public void ResolveWithAnyKeyQuery_InstanceProvided(bool anyKeyQueryBeforeSingletonQueries) + { + var serviceCollection = new ServiceCollection(); + + TestServiceA serviceA1 = new(); + TestServiceA serviceA2 = new(); + TestServiceA serviceA3 = new(); + TestServiceB serviceB1 = new(); + TestServiceB serviceB2 = new(); + TestServiceB serviceB3 = new(); + + // Interweave these to check that the slot \ ordering logic is correct. + // Each unique key + its service Type maintains their own slot in a AnyKey query. + serviceCollection.AddKeyedSingleton("key1", serviceA1); + serviceCollection.AddKeyedSingleton("key1", serviceB1); + serviceCollection.AddKeyedSingleton("key2", serviceA2); + serviceCollection.AddKeyedSingleton("key2", serviceB2); + serviceCollection.AddKeyedSingleton("key3", serviceA3); + serviceCollection.AddKeyedSingleton("key3", serviceB3); + + var provider = CreateServiceProvider(serviceCollection); + + TestServiceA[] allInstancesA = null; + TestServiceB[] allInstancesB = null; + + if (anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + var fromServiceA1 = provider.GetKeyedService("key1"); + var fromServiceA2 = provider.GetKeyedService("key2"); + var fromServiceA3 = provider.GetKeyedService("key3"); + Assert.Same(serviceA1, fromServiceA1); + Assert.Same(serviceA2, fromServiceA2); + Assert.Same(serviceA3, fromServiceA3); + + var fromServiceB1 = provider.GetKeyedService("key1"); + var fromServiceB2 = provider.GetKeyedService("key2"); + var fromServiceB3 = provider.GetKeyedService("key3"); + Assert.Same(serviceB1, fromServiceB1); + Assert.Same(serviceB2, fromServiceB2); + Assert.Same(serviceB3, fromServiceB3); + + if (!anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + Assert.Equal( + new[] { serviceA1, serviceA2, serviceA3 }, + allInstancesA); + + Assert.Equal( + new[] { serviceB1, serviceB2, serviceB3 }, + allInstancesB); + + void DoAnyKeyQuery() + { + IEnumerable allA = provider.GetKeyedServices(KeyedService.AnyKey); + IEnumerable allB = provider.GetKeyedServices(KeyedService.AnyKey); + + // Verify caching returns the same items. + Assert.Equal(allA, provider.GetKeyedServices(KeyedService.AnyKey)); + Assert.Equal(allB, provider.GetKeyedServices(KeyedService.AnyKey)); + + allInstancesA = allA.ToArray(); + allInstancesB = allB.ToArray(); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + // Test ordering and slot assignments when service is provided + // across keyed services with different service types with duplicate keys. + public void ResolveWithAnyKeyQuery_InstanceProvided_Duplicates(bool anyKeyQueryBeforeSingletonQueries) + { + var serviceCollection = new ServiceCollection(); + + TestServiceA serviceA1 = new(); + TestServiceA serviceA2 = new(); + TestServiceA serviceA3 = new(); + TestServiceB serviceB1 = new(); + TestServiceB serviceB2 = new(); + TestServiceB serviceB3 = new(); + + // Interweave these to check that the slot \ ordering logic is correct. + // Each unique key + its service Type maintains their own slot in a AnyKey query. + serviceCollection.AddKeyedSingleton("key", serviceA1); + serviceCollection.AddKeyedSingleton("key", serviceB1); + serviceCollection.AddKeyedSingleton("key", serviceA2); + serviceCollection.AddKeyedSingleton("key", serviceB2); + serviceCollection.AddKeyedSingleton("key", serviceA3); + serviceCollection.AddKeyedSingleton("key", serviceB3); + + var provider = CreateServiceProvider(serviceCollection); + + TestServiceA[] allInstancesA = null; + TestServiceB[] allInstancesB = null; + + if (anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + // We get back the last registered service for duplicates. + Assert.Same(serviceA3, provider.GetKeyedService("key")); + Assert.Same(serviceB3, provider.GetKeyedService("key")); + + if (!anyKeyQueryBeforeSingletonQueries) + { + DoAnyKeyQuery(); + } + + Assert.Equal( + new[] { serviceA1, serviceA2, serviceA3 }, + allInstancesA); + + Assert.Equal( + new[] { serviceB1, serviceB2, serviceB3 }, + allInstancesB); + + void DoAnyKeyQuery() + { + IEnumerable allA = provider.GetKeyedServices(KeyedService.AnyKey); + IEnumerable allB = provider.GetKeyedServices(KeyedService.AnyKey); + + // Verify caching returns the same items. + Assert.Equal(allA, provider.GetKeyedServices(KeyedService.AnyKey)); + Assert.Equal(allB, provider.GetKeyedServices(KeyedService.AnyKey)); + + allInstancesA = allA.ToArray(); + allInstancesB = allB.ToArray(); + } + } + + private class TestServiceA { } + private class TestServiceB { } + [Fact] public void ResolveKeyedServicesAnyKeyOrdering() { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 61b41eb631f022..78f3918460f0d2 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -228,9 +228,9 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica private ServiceCallSite? TryCreateOpenGeneric(ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain) { - if (serviceIdentifier.IsConstructedGenericType) + if (serviceIdentifier.ServiceType.IsConstructedGenericType) { - var genericIdentifier = serviceIdentifier.GetGenericTypeDefinition(); + ServiceIdentifier genericIdentifier = serviceIdentifier.GetGenericTypeDefinition(); if (_descriptorLookup.TryGetValue(genericIdentifier, out ServiceDescriptorCacheItem descriptor)) { return TryCreateOpenGeneric(descriptor.Last, serviceIdentifier, callSiteChain, DefaultSlot, true); @@ -282,7 +282,7 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root; ServiceCallSite[] callSites; - var isAnyKeyLookup = serviceIdentifier.ServiceKey == KeyedService.AnyKey; + bool isAnyKeyLookup = serviceIdentifier.ServiceKey == KeyedService.AnyKey; // If item type is not generic we can safely use descriptor cache // Special case for KeyedService.AnyKey, we don't want to check the cache because a KeyedService.AnyKey registration @@ -291,17 +291,16 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica !isAnyKeyLookup && _descriptorLookup.TryGetValue(cacheKey, out ServiceDescriptorCacheItem descriptors)) { + // Last service will get slot 0. + int slot = descriptors.Count; + callSites = new ServiceCallSite[descriptors.Count]; for (int i = 0; i < descriptors.Count; i++) { ServiceDescriptor descriptor = descriptors[i]; - // Last service should get slot 0 - int slot = descriptors.Count - i - 1; - // There may not be any open generics here - ServiceCallSite? callSite = TryCreateExact(descriptor, cacheKey, callSiteChain, slot); - Debug.Assert(callSite != null); - + // There are no open generics here, so we only need to call CreateExact(). + ServiceCallSite callSite = CreateExact(descriptor, cacheKey, callSiteChain, --slot); cacheLocation = GetCommonCacheLocation(cacheLocation, callSite.Cache.Location); callSites[i] = callSite; } @@ -315,31 +314,41 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica // and open generic matches on the second pass. List> callSitesByIndex = new(); - + Dictionary? keyedSlotAssignment = null; int slot = 0; + + // Do the exact matches first. for (int i = _descriptors.Length - 1; i >= 0; i--) { if (KeysMatch(cacheKey.ServiceKey, _descriptors[i].ServiceKey)) { - // Special case for AnyKey: we don't want to add in cache a mapping AnyKey -> specific type, - // so we need to ask creation with the original identity of the descriptor - var registrationKey = isAnyKeyLookup ? ServiceIdentifier.FromDescriptor(_descriptors[i]) : cacheKey; - if (TryCreateExact(_descriptors[i], registrationKey, callSiteChain, slot) is { } callSite) + if (ShouldCreateExact(_descriptors[i].ServiceType, cacheKey.ServiceType)) { + // For AnyKey, we want to cache based on descriptor identity, not AnyKey that cacheKey has. + ServiceIdentifier registrationKey = isAnyKeyLookup ? ServiceIdentifier.FromDescriptor(_descriptors[i]) : cacheKey; + slot = GetSlot(registrationKey); + ServiceCallSite callSite = CreateExact(_descriptors[i], registrationKey, callSiteChain, slot); AddCallSite(callSite, i); + UpdateSlot(registrationKey); } } } + + // Do the open generic matches second. for (int i = _descriptors.Length - 1; i >= 0; i--) { if (KeysMatch(cacheKey.ServiceKey, _descriptors[i].ServiceKey)) { - // Special case for AnyKey: we don't want to add in cache a mapping AnyKey -> specific type, - // so we need to ask creation with the original identity of the descriptor - var registrationKey = isAnyKeyLookup ? ServiceIdentifier.FromDescriptor(_descriptors[i]) : cacheKey; - if (TryCreateOpenGeneric(_descriptors[i], registrationKey, callSiteChain, slot, throwOnConstraintViolation: false) is { } callSite) + if (ShouldCreateOpenGeneric(_descriptors[i].ServiceType, cacheKey.ServiceType)) { - AddCallSite(callSite, i); + // For AnyKey, we want to cache based on descriptor identity, not AnyKey that cacheKey has. + ServiceIdentifier registrationKey = isAnyKeyLookup ? ServiceIdentifier.FromDescriptor(_descriptors[i]) : cacheKey; + slot = GetSlot(registrationKey); + if (CreateOpenGeneric(_descriptors[i], registrationKey, callSiteChain, slot, throwOnConstraintViolation: false) is { } callSite) + { + AddCallSite(callSite, i); + UpdateSlot(registrationKey); + } } } } @@ -353,15 +362,56 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica void AddCallSite(ServiceCallSite callSite, int index) { - slot++; - cacheLocation = GetCommonCacheLocation(cacheLocation, callSite.Cache.Location); callSitesByIndex.Add(new(index, callSite)); } + + int GetSlot(ServiceIdentifier key) + { + if (!isAnyKeyLookup) + { + return slot; + } + + // Each unique key (including its service type) maintains its own slot counter for ordering and identity. + + if (keyedSlotAssignment is null) + { + keyedSlotAssignment = new Dictionary(capacity: _descriptors.Length) + { + { key, 0 } + }; + + return 0; + } + + if (keyedSlotAssignment.TryGetValue(key, out int existingSlot)) + { + return existingSlot; + } + + keyedSlotAssignment.Add(key, 0); + return 0; + } + + void UpdateSlot(ServiceIdentifier key) + { + if (!isAnyKeyLookup) + { + slot++; + } + else + { + Debug.Assert(keyedSlotAssignment is not null); + keyedSlotAssignment[key] = slot + 1; + } + } } + ResultCache resultCache = (cacheLocation == CallSiteResultCacheLocation.Scope || cacheLocation == CallSiteResultCacheLocation.Root) ? new ResultCache(cacheLocation, callSiteKey) : new ResultCache(CallSiteResultCacheLocation.None, callSiteKey); + return _callSiteCache[callSiteKey] = new IEnumerableCallSite(resultCache, itemType, callSites, serviceIdentifier.ServiceKey); } finally @@ -403,43 +453,66 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult private ServiceCallSite? TryCreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot) { - if (serviceIdentifier.ServiceType == descriptor.ServiceType) + if (ShouldCreateExact(descriptor.ServiceType, serviceIdentifier.ServiceType)) { - ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot); - if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite)) - { - return serviceCallSite; - } + return CreateExact(descriptor, serviceIdentifier, callSiteChain, slot); + } - ServiceCallSite callSite; - var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot); - if (descriptor.HasImplementationInstance()) - { - callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.GetImplementationInstance(), descriptor.ServiceKey); - } - else if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) - { - callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory); - } - else if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null) - { - callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, serviceIdentifier.ServiceKey!, descriptor.KeyedImplementationFactory); - } - else if (descriptor.HasImplementationType()) - { - callSite = CreateConstructorCallSite(lifetime, serviceIdentifier, descriptor.GetImplementationType()!, callSiteChain); - } - else - { - throw new InvalidOperationException(SR.InvalidServiceDescriptor); - } + return null; + } + + private static bool ShouldCreateExact(Type descriptorType, Type serviceType) => + descriptorType == serviceType; + + private ServiceCallSite CreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot) + { + ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot); + if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite)) + { + return serviceCallSite; + } - return _callSiteCache[callSiteKey] = callSite; + ServiceCallSite callSite; + var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot); + if (descriptor.HasImplementationInstance()) + { + callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.GetImplementationInstance(), descriptor.ServiceKey); + } + else if (!descriptor.IsKeyedService && descriptor.ImplementationFactory != null) + { + callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory); + } + else if (descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null) + { + callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, serviceIdentifier.ServiceKey!, descriptor.KeyedImplementationFactory); + } + else if (descriptor.HasImplementationType()) + { + callSite = CreateConstructorCallSite(lifetime, serviceIdentifier, descriptor.GetImplementationType()!, callSiteChain); + } + else + { + throw new InvalidOperationException(SR.InvalidServiceDescriptor); + } + + Debug.Assert(callSite != null); + return _callSiteCache[callSiteKey] = callSite; + } + + private ServiceCallSite? TryCreateOpenGeneric(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation) + { + if (ShouldCreateOpenGeneric(descriptor.ServiceType, serviceIdentifier.ServiceType)) + { + return CreateOpenGeneric(descriptor, serviceIdentifier, callSiteChain, slot, throwOnConstraintViolation); } return null; } + private static bool ShouldCreateOpenGeneric(Type descriptorType, Type serviceType) => + serviceType.IsConstructedGenericType && + serviceType.GetGenericTypeDefinition() == descriptorType; + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType", Justification = "MakeGenericType here is used to create a closed generic implementation type given the closed service type. " + "Trimming annotations on the generic types are verified when 'Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability' is set, which is set by default when PublishTrimmed=true. " + @@ -447,45 +520,41 @@ private static CallSiteResultCacheLocation GetCommonCacheLocation(CallSiteResult [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, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation) + private ServiceCallSite? CreateOpenGeneric(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, int slot, bool throwOnConstraintViolation) { - if (serviceIdentifier.IsConstructedGenericType && - serviceIdentifier.ServiceType.GetGenericTypeDefinition() == descriptor.ServiceType) + ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot); + if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite)) { - ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceIdentifier, slot); - if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite)) - { - return serviceCallSite; - } + return serviceCallSite; + } - Type? implementationType = descriptor.GetImplementationType(); - Debug.Assert(implementationType != null, "descriptor.ImplementationType != null"); - var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot); - Type closedType; - try + Type? implementationType = descriptor.GetImplementationType(); + Debug.Assert(implementationType != null, "descriptor.ImplementationType != null"); + var lifetime = new ResultCache(descriptor.Lifetime, serviceIdentifier, slot); + Type closedType; + try + { + Type[] genericTypeArguments = serviceIdentifier.ServiceType.GenericTypeArguments; + if (ServiceProvider.VerifyAotCompatibility) { - Type[] genericTypeArguments = serviceIdentifier.ServiceType.GenericTypeArguments; - if (ServiceProvider.VerifyAotCompatibility) - { - VerifyOpenGenericAotCompatibility(serviceIdentifier.ServiceType, genericTypeArguments); - } - - closedType = implementationType.MakeGenericType(genericTypeArguments); + VerifyOpenGenericAotCompatibility(serviceIdentifier.ServiceType, genericTypeArguments); } - catch (ArgumentException) - { - if (throwOnConstraintViolation) - { - throw; - } - return null; + closedType = implementationType.MakeGenericType(genericTypeArguments); + } + catch (ArgumentException) + { + if (throwOnConstraintViolation) + { + throw; } - return _callSiteCache[callSiteKey] = CreateConstructorCallSite(lifetime, serviceIdentifier, closedType, callSiteChain); + return null; } - return null; + ConstructorCallSite site = CreateConstructorCallSite(lifetime, serviceIdentifier, closedType, callSiteChain); + Debug.Assert(site != null); + return _callSiteCache[callSiteKey] = site; } private ConstructorCallSite CreateConstructorCallSite( diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs index 0b87443248ad9d..c0b853baf9923d 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceIdentifier.cs @@ -58,8 +58,6 @@ public override int GetHashCode() } } - public bool IsConstructedGenericType => ServiceType.IsConstructedGenericType; - public ServiceIdentifier GetGenericTypeDefinition() => new ServiceIdentifier(ServiceKey, ServiceType.GetGenericTypeDefinition()); public override string? ToString()