diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index b64a8a9b9f7ba2..490337d14b028c 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -344,11 +344,21 @@ private static bool AreCompatible(DynamicallyAccessedMemberTypes serviceDynamica // 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) + + // We skip open generics with incompatible constraints. + if (CreateOpenGeneric(_descriptors[i], registrationKey, callSiteChain, slot, false) is { } callSite) { AddCallSite(callSite, i); UpdateSlot(registrationKey); } + else if (slot == 0) + { + // If the last registration has incompatible constraints, we still need to update the slot. + // This ensures that single service resolution (GetService) will attempt to resolve using the last + // registration and throw an ArgumentException, maintaining "last wins" semantics. During enumerable + // resolution (GetServices), the incompatible registration is simply skipped. + UpdateSlot(registrationKey); + } } } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs index 94802673078634..2e837783951f7f 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs @@ -1312,6 +1312,18 @@ public void ResolveKeyedServiceWithKeyedParameter_MissingRegistrationButWithUnke Assert.Contains("Microsoft.Extensions.DependencyInjection.Specification.KeyedDependencyInjectionSpecificationTests+IService", ex.ToString()); } + [Fact] + public void InvalidConstrainedOpenGenericIsSkippedInEnumerableButThrowsInSingleResolution() + { + var sc = new ServiceCollection(); + sc.AddSingleton(typeof(IBB<>), typeof(GenericBB<>)); + sc.AddSingleton(typeof(IBB<>), typeof(ConstrainedGenericBB<>)); + var sp = CreateServiceProvider(sc); + + Assert.Single(sp.GetServices>>()); + Assert.Throws(() => sp.GetService>>()); + } + private async Task ResolveUniqueServicesConcurrently() { var types = new Type[]