From 5132b4d515f6475f3784bf6abc477e935b25c7df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 07:48:31 +0000 Subject: [PATCH 01/20] Initial plan From 8323b0a5d539c7dd682f57c95bfe2226e22a8af1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 08:30:58 +0000 Subject: [PATCH 02/20] Make CallSiteFactory constructor selection deterministic in reflection order Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 86 +++++++++++--- .../ServiceLookup/CallSiteFactoryTest.cs | 111 ++++++++++++++++++ 2 files changed, 182 insertions(+), 15 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 3c3d3c3e897cc4..5980f18c7ee21f 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -603,14 +603,28 @@ private ConstructorCallSite CreateConstructorCallSite( return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites, serviceIdentifier.ServiceKey); } - Array.Sort(constructors, - (a, b) => b.GetParameters().Length.CompareTo(a.GetParameters().Length)); - ConstructorInfo? bestConstructor = null; + ParameterInfo[]? bestConstructorParameters = null; HashSet? bestConstructorParameterTypes = null; + int bestConstructorParameterLength = -1; + ConstructorInfo? ambiguousConstructor = null; + ConstructorInfo? bestConstructorForAmbiguousConstructor = null; + List>? resolvedConstructors = null; + + ParameterInfo[][] parametersByConstructor = new ParameterInfo[constructors.Length][]; + for (int i = 0; i < constructors.Length; i++) + { + parametersByConstructor[i] = constructors[i].GetParameters(); + } + for (int i = 0; i < constructors.Length; i++) { - ParameterInfo[] parameters = constructors[i].GetParameters(); + ParameterInfo[] parameters = parametersByConstructor[i]; + + if (bestConstructor is not null && parameters.Length < bestConstructorParameterLength) + { + continue; + } ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, @@ -621,20 +635,57 @@ private ConstructorCallSite CreateConstructorCallSite( if (currentParameterCallSites != null) { - if (bestConstructor == null) + resolvedConstructors ??= new List>(); + resolvedConstructors.Add(new KeyValuePair(constructors[i], parameters)); + + if (bestConstructor is null || parameters.Length > bestConstructorParameterLength) { bestConstructor = constructors[i]; + bestConstructorParameters = parameters; + bestConstructorParameterLength = parameters.Length; parameterCallSites = currentParameterCallSites; + bestConstructorParameterTypes = null; + ambiguousConstructor = null; + bestConstructorForAmbiguousConstructor = null; + + if (resolvedConstructors.Count > 1) + { + bestConstructorParameterTypes = new HashSet(); + foreach (ParameterInfo p in bestConstructorParameters) + { + bestConstructorParameterTypes.Add(p.ParameterType); + } + + foreach (KeyValuePair resolvedConstructor in resolvedConstructors) + { + if (resolvedConstructor.Key == bestConstructor) + { + continue; + } + + foreach (ParameterInfo p in resolvedConstructor.Value) + { + if (!bestConstructorParameterTypes.Contains(p.ParameterType)) + { + ambiguousConstructor = resolvedConstructor.Key; + bestConstructorForAmbiguousConstructor = bestConstructor; + break; + } + } + + if (ambiguousConstructor is not null) + { + break; + } + } + } } else { - // Since we're visiting constructors in decreasing order of number of parameters, - // we'll only see ambiguities or supersets once we've seen a 'bestConstructor'. - if (bestConstructorParameterTypes == null) { bestConstructorParameterTypes = new HashSet(); - foreach (ParameterInfo p in bestConstructor.GetParameters()) + foreach (ParameterInfo p in bestConstructorParameters!) { bestConstructorParameterTypes.Add(p.ParameterType); } @@ -644,12 +695,9 @@ private ConstructorCallSite CreateConstructorCallSite( { if (!bestConstructorParameterTypes.Contains(p.ParameterType)) { - // Ambiguous match exception - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructor, - constructors[i])); + ambiguousConstructor ??= constructors[i]; + bestConstructorForAmbiguousConstructor ??= bestConstructor; + break; } } } @@ -661,6 +709,14 @@ private ConstructorCallSite CreateConstructorCallSite( throw new InvalidOperationException( SR.Format(SR.UnableToActivateTypeException, implementationType)); } + else if (ambiguousConstructor != null) + { + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructorForAmbiguousConstructor, + ambiguousConstructor)); + } else { Debug.Assert(parameterCallSites != null); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index c8ed86dd37cd3d..0fe6ea363177b3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -137,6 +137,70 @@ public void CreateCallSite_UsesNullaryConstructorIfServicesCannotBeInjectedIntoO Assert.Empty(ctorCallSite.ParameterCallSites); } + [Fact] + public void ServiceProvider_UsesDeclarationOrderForSameArityConstructorsWithSameParameterTypes() + { + var collection = new ServiceCollection(); + collection.AddTransient(); + collection.AddTransient(); + collection.AddTransient(); + + using ServiceProvider provider = collection.BuildServiceProvider(); + var service = provider.GetRequiredService(); + + Assert.Equal(1, service.SelectedConstructor); + } + + [Fact] + public void CreateCallSite_ThrowsIfMultipleSameArityDisjointConstructorsCanBeResolved() + { + // Arrange + var type = typeof(TypeWithSameArityDisjointConstructors); + var expectedMessage = + string.Join( + Environment.NewLine, + $"Unable to activate type '{type}'. The following constructors are ambiguous:", + GetConstructor(type, new[] { typeof(IFakeService), typeof(IFakeScopedService) }), + GetConstructor(type, new[] { typeof(IFactoryService), typeof(IFakeMultipleService) })); + + var callSiteFactory = GetCallSiteFactory( + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeScopedService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFactoryService), typeof(TransientFactoryService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeMultipleService), typeof(FakeService), ServiceLifetime.Transient)); + + // Act and Assert + var ex = Assert.Throws( + () => callSiteFactory(type)); + Assert.Equal(expectedMessage, ex.Message); + } + + [Theory] + [InlineData(typeof(TypeWithShortThenLongResolvableConstructors), true)] + [InlineData(typeof(TypeWithShortThenLongUnresolvableConstructors), false)] + public void CreateCallSite_UsesLongestResolvableConstructorWhenDeclaredAfterShorter(Type type, bool expectLongConstructor) + { + // Arrange + var descriptor = new ServiceDescriptor(type, type, ServiceLifetime.Transient); + var callSiteFactory = GetCallSiteFactory( + descriptor, + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFactoryService), typeof(TransientFactoryService), ServiceLifetime.Transient)); + + Type[] expectedParameters = expectLongConstructor + ? new[] { typeof(IFakeService), typeof(IFactoryService) } + : new[] { typeof(IFakeService) }; + + // Act + var callSite = (ServiceCallSite)callSiteFactory(type); + + // Assert + Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); + var constructorCallSite = Assert.IsType(callSite); + Assert.Equal(expectedParameters, GetParameters(constructorCallSite)); + } + [Fact] public void CreateCallSite_Throws_IfClosedTypeDoesNotSatisfyStructGenericConstraint() { @@ -1021,6 +1085,53 @@ private class Class2 { public Class2(Class3 c3) { } } private class Class3 { } private class Class4 { public Class4(Class3 c3) { } } private class Class5 { public Class5(Class2 c2) { } } + private class TypeWithSameArityConstructorsWithSameParameterTypes + { + public TypeWithSameArityConstructorsWithSameParameterTypes(IFakeService fakeService, IFakeMultipleService fakeMultipleService) + { + SelectedConstructor = 1; + } + + public TypeWithSameArityConstructorsWithSameParameterTypes(IFakeMultipleService fakeMultipleService, IFakeService fakeService) + { + SelectedConstructor = 2; + } + + public int SelectedConstructor { get; } + } + + private class TypeWithSameArityDisjointConstructors + { + public TypeWithSameArityDisjointConstructors(IFakeService fakeService, IFakeScopedService fakeScopedService) + { + } + + public TypeWithSameArityDisjointConstructors(IFactoryService factoryService, IFakeMultipleService fakeMultipleService) + { + } + } + + private class TypeWithShortThenLongResolvableConstructors + { + public TypeWithShortThenLongResolvableConstructors(IFakeService fakeService) + { + } + + public TypeWithShortThenLongResolvableConstructors(IFakeService fakeService, IFactoryService factoryService) + { + } + } + + private class TypeWithShortThenLongUnresolvableConstructors + { + public TypeWithShortThenLongUnresolvableConstructors(IFakeService fakeService) + { + } + + public TypeWithShortThenLongUnresolvableConstructors(IFakeService fakeService, IFakeOuterService fakeOuterService) + { + } + } private record struct Struct1(int Value) { } From d33c5b877a18591062cab65da435908937ed198e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:23:21 +0000 Subject: [PATCH 03/20] Redesign constructor selection with stable arity ordering and parameter-aware ambiguity Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 364 +++++++++++------- .../ServiceLookup/CallSiteFactoryTest.cs | 100 +++++ 2 files changed, 320 insertions(+), 144 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 5980f18c7ee21f..087064f8d51228 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -603,125 +603,96 @@ private ConstructorCallSite CreateConstructorCallSite( return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites, serviceIdentifier.ServiceKey); } - ConstructorInfo? bestConstructor = null; - ParameterInfo[]? bestConstructorParameters = null; - HashSet? bestConstructorParameterTypes = null; - int bestConstructorParameterLength = -1; - ConstructorInfo? ambiguousConstructor = null; - ConstructorInfo? bestConstructorForAmbiguousConstructor = null; - List>? resolvedConstructors = null; - - ParameterInfo[][] parametersByConstructor = new ParameterInfo[constructors.Length][]; - for (int i = 0; i < constructors.Length; i++) + int constructorCount = constructors.Length; + var sortedConstructors = new ConstructorInfo[constructorCount]; + var sortedParameters = new ParameterInfo[constructorCount][]; + for (int i = 0; i < constructorCount; i++) { - parametersByConstructor[i] = constructors[i].GetParameters(); + ConstructorInfo constructor = constructors[i]; + ParameterInfo[] parameters = constructor.GetParameters(); + + int sortedIndex = i; + while (sortedIndex > 0 && sortedParameters[sortedIndex - 1].Length < parameters.Length) + { + sortedConstructors[sortedIndex] = sortedConstructors[sortedIndex - 1]; + sortedParameters[sortedIndex] = sortedParameters[sortedIndex - 1]; + sortedIndex--; + } + + sortedConstructors[sortedIndex] = constructor; + sortedParameters[sortedIndex] = parameters; } - for (int i = 0; i < constructors.Length; i++) + ConstructorInfo? bestConstructor = null; + int bestConstructorParameterLength = -1; + HashSet? bestParametersResolvedFromCallSite = null; + HashSet? bestParametersResolvedFromDefault = null; + + for (int i = 0; i < constructorCount; i++) { - ParameterInfo[] parameters = parametersByConstructor[i]; + ConstructorInfo constructor = sortedConstructors[i]; + ParameterInfo[] parameters = sortedParameters[i]; - if (bestConstructor is not null && parameters.Length < bestConstructorParameterLength) + if (bestConstructor is null) { + var currentParametersResolvedFromCallSite = new HashSet(); + var currentParametersResolvedFromDefault = new HashSet(); + ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( + serviceIdentifier, + implementationType, + callSiteChain, + parameters, + throwIfCallSiteNotFound: false, + currentParametersResolvedFromCallSite, + currentParametersResolvedFromDefault); + + if (currentParameterCallSites is null) + { + continue; + } + + bestConstructor = constructor; + bestConstructorParameterLength = parameters.Length; + parameterCallSites = currentParameterCallSites; + bestParametersResolvedFromCallSite = currentParametersResolvedFromCallSite; + bestParametersResolvedFromDefault = currentParametersResolvedFromDefault; continue; } - ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( + Debug.Assert(bestParametersResolvedFromCallSite is not null); + Debug.Assert(bestParametersResolvedFromDefault is not null); + + if (!TryResolveConstructorWithoutCreatingArgumentCallSites( serviceIdentifier, implementationType, callSiteChain, parameters, - throwIfCallSiteNotFound: false); - - if (currentParameterCallSites != null) + bestParametersResolvedFromCallSite, + bestParametersResolvedFromDefault, + out bool hasNewResolvableParameter)) { - resolvedConstructors ??= new List>(); - resolvedConstructors.Add(new KeyValuePair(constructors[i], parameters)); - - if (bestConstructor is null || parameters.Length > bestConstructorParameterLength) - { - bestConstructor = constructors[i]; - bestConstructorParameters = parameters; - bestConstructorParameterLength = parameters.Length; - parameterCallSites = currentParameterCallSites; - bestConstructorParameterTypes = null; - ambiguousConstructor = null; - bestConstructorForAmbiguousConstructor = null; - - if (resolvedConstructors.Count > 1) - { - bestConstructorParameterTypes = new HashSet(); - foreach (ParameterInfo p in bestConstructorParameters) - { - bestConstructorParameterTypes.Add(p.ParameterType); - } - - foreach (KeyValuePair resolvedConstructor in resolvedConstructors) - { - if (resolvedConstructor.Key == bestConstructor) - { - continue; - } - - foreach (ParameterInfo p in resolvedConstructor.Value) - { - if (!bestConstructorParameterTypes.Contains(p.ParameterType)) - { - ambiguousConstructor = resolvedConstructor.Key; - bestConstructorForAmbiguousConstructor = bestConstructor; - break; - } - } - - if (ambiguousConstructor is not null) - { - break; - } - } - } - } - else - { - if (bestConstructorParameterTypes == null) - { - bestConstructorParameterTypes = new HashSet(); - foreach (ParameterInfo p in bestConstructorParameters!) - { - bestConstructorParameterTypes.Add(p.ParameterType); - } - } + continue; + } - foreach (ParameterInfo p in parameters) - { - if (!bestConstructorParameterTypes.Contains(p.ParameterType)) - { - ambiguousConstructor ??= constructors[i]; - bestConstructorForAmbiguousConstructor ??= bestConstructor; - break; - } - } - } + if (hasNewResolvableParameter) + { + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructor, + constructor)); } } - if (bestConstructor == null) + if (bestConstructor is null) { throw new InvalidOperationException( SR.Format(SR.UnableToActivateTypeException, implementationType)); } - else if (ambiguousConstructor != null) - { - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructorForAmbiguousConstructor, - ambiguousConstructor)); - } - else - { - Debug.Assert(parameterCallSites != null); - return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, bestConstructor, parameterCallSites, serviceIdentifier.ServiceKey); - } + + Debug.Assert(parameterCallSites != null); + Debug.Assert(bestConstructorParameterLength >= 0); + return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, bestConstructor, parameterCallSites, serviceIdentifier.ServiceKey); } finally { @@ -735,80 +706,185 @@ private ConstructorCallSite CreateConstructorCallSite( Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, - bool throwIfCallSiteNotFound) + bool throwIfCallSiteNotFound, + HashSet? parametersResolvedFromCallSite = null, + HashSet? parametersResolvedFromDefault = null) { var parameterCallSites = new ServiceCallSite[parameters.Length]; for (int index = 0; index < parameters.Length; index++) { - ServiceCallSite? callSite = null; - bool isKeyedParameter = false; - Type parameterType = parameters[index].ParameterType; - foreach (var attribute in parameters[index].GetCustomAttributes(true)) + if (!TryResolveCallSite( + serviceIdentifier, + implementationType, + callSiteChain, + parameters[index], + throwIfCallSiteNotFound, + createCallSite: true, + out ServiceIdentifier parameterServiceIdentifier, + out ParameterResolutionKind parameterResolutionKind, + out ServiceCallSite? callSite)) { - if (serviceIdentifier.ServiceKey != null && attribute is ServiceKeyAttribute) - { - // Even though the parameter may be strongly typed, support 'object' if AnyKey is used. + return null; + } - if (serviceIdentifier.ServiceKey == KeyedService.AnyKey) - { - parameterType = typeof(object); - } - else if (parameterType != serviceIdentifier.ServiceKey.GetType() - && parameterType != typeof(object)) - { - throw new InvalidOperationException(SR.InvalidServiceKeyType); - } + Debug.Assert(callSite is not null); + if (parameterResolutionKind == ParameterResolutionKind.FromCallSite) + { + parametersResolvedFromCallSite?.Add(parameterServiceIdentifier); + } + else + { + Debug.Assert(parameterResolutionKind == ParameterResolutionKind.FromDefaultValue); + parametersResolvedFromDefault?.Add(parameterServiceIdentifier); + } - callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); - break; - } + parameterCallSites[index] = callSite; + } + + return parameterCallSites; + } + + private bool TryResolveConstructorWithoutCreatingArgumentCallSites( + ServiceIdentifier serviceIdentifier, + Type implementationType, + CallSiteChain callSiteChain, + ParameterInfo[] parameters, + HashSet bestParametersResolvedFromCallSite, + HashSet bestParametersResolvedFromDefault, + out bool hasNewResolvableParameter) + { + hasNewResolvableParameter = false; + + for (int i = 0; i < parameters.Length; i++) + { + if (!TryResolveCallSite( + serviceIdentifier, + implementationType, + callSiteChain, + parameters[i], + throwIfCallSiteNotFound: false, + createCallSite: false, + out ServiceIdentifier parameterServiceIdentifier, + out ParameterResolutionKind parameterResolutionKind, + out _)) + { + return false; + } - if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) + if (parameterResolutionKind == ParameterResolutionKind.FromCallSite) + { + if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) { - object? serviceKey = fromKeyedServicesAttribute.LookupMode switch - { - ServiceKeyLookupMode.InheritKey => serviceIdentifier.ServiceKey, - ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, - ServiceKeyLookupMode.NullKey => null, - _ => null - }; + continue; + } - if (serviceKey is not null) - { - callSite = GetCallSite(new ServiceIdentifier(serviceKey, parameterType), callSiteChain); - isKeyedParameter = true; - break; - } + if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + { + return false; } + + hasNewResolvableParameter = true; + continue; } - if (!isKeyedParameter) + Debug.Assert(parameterResolutionKind == ParameterResolutionKind.FromDefaultValue); + if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier) || + bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) { - callSite ??= GetCallSite(ServiceIdentifier.FromServiceType(parameterType), callSiteChain); + continue; } - if (callSite == null && ParameterDefaultValue.TryGetDefaultValue(parameters[index], out object? defaultValue)) + hasNewResolvableParameter = true; + } + + return true; + } + + private bool TryResolveCallSite( + ServiceIdentifier serviceIdentifier, + Type implementationType, + CallSiteChain callSiteChain, + ParameterInfo parameter, + bool throwIfCallSiteNotFound, + bool createCallSite, + out ServiceIdentifier parameterServiceIdentifier, + out ParameterResolutionKind parameterResolutionKind, + out ServiceCallSite? callSite) + { + Type parameterType = parameter.ParameterType; + parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); + + foreach (object attribute in parameter.GetCustomAttributes(true)) + { + if (serviceIdentifier.ServiceKey != null && attribute is ServiceKeyAttribute) { - callSite = new ConstantCallSite(parameterType, defaultValue); + // Even though the parameter may be strongly typed, support 'object' if AnyKey is used. + if (serviceIdentifier.ServiceKey == KeyedService.AnyKey) + { + parameterType = typeof(object); + } + else if (parameterType != serviceIdentifier.ServiceKey.GetType() && + parameterType != typeof(object)) + { + throw new InvalidOperationException(SR.InvalidServiceKeyType); + } + + parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); + parameterResolutionKind = ParameterResolutionKind.FromCallSite; + callSite = createCallSite ? new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey) : null; + return true; } - if (callSite == null) + if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) { - if (throwIfCallSiteNotFound) + object? serviceKey = fromKeyedServicesAttribute.LookupMode switch { - throw new InvalidOperationException(SR.Format(SR.CannotResolveService, - parameterType, - implementationType)); - } + ServiceKeyLookupMode.InheritKey => serviceIdentifier.ServiceKey, + ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, + ServiceKeyLookupMode.NullKey => null, + _ => null + }; - return null; + if (serviceKey is not null) + { + parameterServiceIdentifier = new ServiceIdentifier(serviceKey, parameterType); + } } + } - parameterCallSites[index] = callSite; + ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); + if (parameterCallSite is not null) + { + parameterResolutionKind = ParameterResolutionKind.FromCallSite; + callSite = createCallSite ? parameterCallSite : null; + return true; } - return parameterCallSites; + if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) + { + parameterResolutionKind = ParameterResolutionKind.FromDefaultValue; + callSite = createCallSite ? new ConstantCallSite(parameterType, defaultValue) : null; + return true; + } + + if (throwIfCallSiteNotFound) + { + throw new InvalidOperationException(SR.Format(SR.CannotResolveService, + parameterType, + implementationType)); + } + + parameterResolutionKind = ParameterResolutionKind.Unresolved; + callSite = null; + return false; + } + + private enum ParameterResolutionKind + { + Unresolved, + FromCallSite, + FromDefaultValue, } /// diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 0fe6ea363177b3..3d2423b097ee04 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -176,6 +176,62 @@ public void CreateCallSite_ThrowsIfMultipleSameArityDisjointConstructorsCanBeRes Assert.Equal(expectedMessage, ex.Message); } + [Theory] + [InlineData(typeof(TypeWithCrossArityDisjointConstructorsLongFirst))] + [InlineData(typeof(TypeWithCrossArityDisjointConstructorsShortFirst))] + public void CreateCallSite_ThrowsIfCrossArityDisjointConstructorsCanBeResolved(Type type) + { + // Arrange + var expectedMessage = + string.Join( + Environment.NewLine, + $"Unable to activate type '{type}'. The following constructors are ambiguous:", + GetConstructor(type, new[] { typeof(IFakeService), typeof(IFactoryService), typeof(IFakeScopedService) }), + GetConstructor(type, new[] { typeof(IFakeOuterService) })); + + var callSiteFactory = GetCallSiteFactory( + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFactoryService), typeof(TransientFactoryService), ServiceLifetime.Transient)); + + // Act and Assert + var ex = Assert.Throws( + () => callSiteFactory(type)); + Assert.Equal(expectedMessage, ex.Message); + } + + [Fact] + public void CreateCallSite_ThrowsIfSameTypeParametersUseDifferentServiceKeys() + { + // Arrange + var type = typeof(TypeWithSameTypeDifferentServiceKeyConstructors); + var callSiteFactory = GetCallSiteFactory( + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + ServiceDescriptor.KeyedTransient("a"), + ServiceDescriptor.KeyedTransient("b")); + + // Act and Assert + var ex = Assert.Throws(() => callSiteFactory(type)); + Assert.StartsWith($"Unable to activate type '{type}'. The following constructors are ambiguous:", ex.Message); + } + + [Fact] + public void CreateCallSite_IgnoresSmallerConstructorWhenBestUsesDefaultAndSmallerNeedsContainer() + { + // Arrange + var type = typeof(TypeWithDefaultInBestAndNonDefaultInSmallerConstructors); + var callSiteFactory = GetCallSiteFactory( + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient)); + + // Act + var callSite = (ServiceCallSite)callSiteFactory(type); + + // Assert + var constructorCallSite = Assert.IsType(callSite); + Assert.Equal(new[] { typeof(IFakeService), typeof(IFakeScopedService) }, GetParameters(constructorCallSite)); + } + [Theory] [InlineData(typeof(TypeWithShortThenLongResolvableConstructors), true)] [InlineData(typeof(TypeWithShortThenLongUnresolvableConstructors), false)] @@ -1133,6 +1189,50 @@ public TypeWithShortThenLongUnresolvableConstructors(IFakeService fakeService, I } } + private class TypeWithCrossArityDisjointConstructorsLongFirst + { + public TypeWithCrossArityDisjointConstructorsLongFirst(IFakeService fakeService, IFactoryService factoryService, IFakeScopedService fakeScopedService = null) + { + } + + public TypeWithCrossArityDisjointConstructorsLongFirst(IFakeOuterService fakeOuterService = null) + { + } + } + + private class TypeWithCrossArityDisjointConstructorsShortFirst + { + public TypeWithCrossArityDisjointConstructorsShortFirst(IFakeOuterService fakeOuterService = null) + { + } + + public TypeWithCrossArityDisjointConstructorsShortFirst(IFakeService fakeService, IFactoryService factoryService, IFakeScopedService fakeScopedService = null) + { + } + } + + private class TypeWithSameTypeDifferentServiceKeyConstructors + { + public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("a")] IFakeService service) + { + } + + public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("b")] IFakeService service) + { + } + } + + private class TypeWithDefaultInBestAndNonDefaultInSmallerConstructors + { + public TypeWithDefaultInBestAndNonDefaultInSmallerConstructors(IFakeService fakeService, IFakeScopedService fakeScopedService = null) + { + } + + public TypeWithDefaultInBestAndNonDefaultInSmallerConstructors(IFakeScopedService fakeScopedService) + { + } + } + private record struct Struct1(int Value) { } // Open generic From 3e1a35f3a94c7e0a7e2cfa5c0f0e17700e805826 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 08:18:42 +0200 Subject: [PATCH 04/20] fix implementattion --- .../src/ServiceLookup/CallSiteFactory.cs | 98 ++++++++++++++----- .../ServiceLookup/CallSiteFactoryTest.cs | 8 +- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 087064f8d51228..9a4e1ab3dbe235 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -664,7 +664,6 @@ private ConstructorCallSite CreateConstructorCallSite( if (!TryResolveConstructorWithoutCreatingArgumentCallSites( serviceIdentifier, - implementationType, callSiteChain, parameters, bestParametersResolvedFromCallSite, @@ -720,7 +719,6 @@ private ConstructorCallSite CreateConstructorCallSite( callSiteChain, parameters[index], throwIfCallSiteNotFound, - createCallSite: true, out ServiceIdentifier parameterServiceIdentifier, out ParameterResolutionKind parameterResolutionKind, out ServiceCallSite? callSite)) @@ -747,7 +745,6 @@ private ConstructorCallSite CreateConstructorCallSite( private bool TryResolveConstructorWithoutCreatingArgumentCallSites( ServiceIdentifier serviceIdentifier, - Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, HashSet bestParametersResolvedFromCallSite, @@ -758,44 +755,92 @@ private bool TryResolveConstructorWithoutCreatingArgumentCallSites( for (int i = 0; i < parameters.Length; i++) { - if (!TryResolveCallSite( - serviceIdentifier, - implementationType, - callSiteChain, - parameters[i], - throwIfCallSiteNotFound: false, - createCallSite: false, - out ServiceIdentifier parameterServiceIdentifier, - out ParameterResolutionKind parameterResolutionKind, - out _)) + ParameterInfo parameter = parameters[i]; + Type parameterType = parameter.ParameterType; + ServiceIdentifier parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); + bool isImmediatelyResolved = false; + + foreach (object attribute in parameter.GetCustomAttributes(true)) { - return false; + if (serviceIdentifier.ServiceKey is not null && attribute is ServiceKeyAttribute) + { + if (serviceIdentifier.ServiceKey == KeyedService.AnyKey) + { + parameterType = typeof(object); + } + else if (parameterType != serviceIdentifier.ServiceKey.GetType() && + parameterType != typeof(object)) + { + throw new InvalidOperationException(SR.InvalidServiceKeyType); + } + + parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); + isImmediatelyResolved = true; + break; + } + + if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) + { + object? serviceKey = fromKeyedServicesAttribute.LookupMode switch + { + ServiceKeyLookupMode.InheritKey => serviceIdentifier.ServiceKey, + ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, + ServiceKeyLookupMode.NullKey => null, + _ => null + }; + + if (serviceKey is not null) + { + parameterServiceIdentifier = new ServiceIdentifier(serviceKey, parameterType); + } + + break; + } } - if (parameterResolutionKind == ParameterResolutionKind.FromCallSite) + if (isImmediatelyResolved) { - if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) + // [ServiceKey]: always resolvable via constant injection. + if (!bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) { - continue; + hasNewResolvableParameter = true; } - if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + continue; + } + + if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) + { + // Container has this service; resolvable and not new. + continue; + } + + if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + { + // Container doesn't have this (best fell back to its default). + // Resolvable only if this parameter also has a default value. + if (ParameterDefaultValue.TryGetDefaultValue(parameter, out _)) { - return false; + continue; } + return false; + } + + // Unknown identity; must probe the container. + if (GetCallSite(parameterServiceIdentifier, callSiteChain) is not null) + { hasNewResolvableParameter = true; continue; } - Debug.Assert(parameterResolutionKind == ParameterResolutionKind.FromDefaultValue); - if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier) || - bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + if (ParameterDefaultValue.TryGetDefaultValue(parameter, out _)) { + hasNewResolvableParameter = true; continue; } - hasNewResolvableParameter = true; + return false; } return true; @@ -807,7 +852,6 @@ private bool TryResolveCallSite( CallSiteChain callSiteChain, ParameterInfo parameter, bool throwIfCallSiteNotFound, - bool createCallSite, out ServiceIdentifier parameterServiceIdentifier, out ParameterResolutionKind parameterResolutionKind, out ServiceCallSite? callSite) @@ -832,7 +876,7 @@ private bool TryResolveCallSite( parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); parameterResolutionKind = ParameterResolutionKind.FromCallSite; - callSite = createCallSite ? new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey) : null; + callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); return true; } @@ -857,14 +901,14 @@ private bool TryResolveCallSite( if (parameterCallSite is not null) { parameterResolutionKind = ParameterResolutionKind.FromCallSite; - callSite = createCallSite ? parameterCallSite : null; + callSite = parameterCallSite; return true; } if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) { parameterResolutionKind = ParameterResolutionKind.FromDefaultValue; - callSite = createCallSite ? new ConstantCallSite(parameterType, defaultValue) : null; + callSite = new ConstantCallSite(parameterType, defaultValue); return true; } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 3d2423b097ee04..f1f262bd9f9021 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -208,7 +208,9 @@ public void CreateCallSite_ThrowsIfSameTypeParametersUseDifferentServiceKeys() var callSiteFactory = GetCallSiteFactory( new ServiceDescriptor(type, type, ServiceLifetime.Transient), ServiceDescriptor.KeyedTransient("a"), - ServiceDescriptor.KeyedTransient("b")); + ServiceDescriptor.KeyedTransient("b"), + new ServiceDescriptor(typeof(IFakeScopedService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeMultipleService), typeof(FakeService), ServiceLifetime.Transient)); // Act and Assert var ex = Assert.Throws(() => callSiteFactory(type)); @@ -1213,11 +1215,11 @@ public TypeWithCrossArityDisjointConstructorsShortFirst(IFakeService fakeService private class TypeWithSameTypeDifferentServiceKeyConstructors { - public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("a")] IFakeService service) + public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("a")] IFakeService service, IFakeScopedService scoped) { } - public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("b")] IFakeService service) + public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("b")] IFakeService service, IFakeMultipleService multiple) { } } From 3c65478411fc7199d9cefe9b3fcac740e92dc176 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 08:51:30 +0200 Subject: [PATCH 05/20] simplify --- .../src/ServiceLookup/CallSiteFactory.cs | 128 ++++++------------ 1 file changed, 43 insertions(+), 85 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 9a4e1ab3dbe235..e0c645aef66d31 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -662,18 +662,19 @@ private ConstructorCallSite CreateConstructorCallSite( Debug.Assert(bestParametersResolvedFromCallSite is not null); Debug.Assert(bestParametersResolvedFromDefault is not null); - if (!TryResolveConstructorWithoutCreatingArgumentCallSites( + if (CreateArgumentCallSites( serviceIdentifier, + implementationType, callSiteChain, parameters, - bestParametersResolvedFromCallSite, - bestParametersResolvedFromDefault, - out bool hasNewResolvableParameter)) + throwIfCallSiteNotFound: false) is null) { + // Not all parameters are resolvable; skip this constructor. continue; } - if (hasNewResolvableParameter) + // All parameters resolvable; check if it's a strict subset of best. + if (!ParameterSetIsSubsetOfBest(parameters, serviceIdentifier, bestParametersResolvedFromCallSite, bestParametersResolvedFromDefault)) { throw new InvalidOperationException(string.Join( Environment.NewLine, @@ -743,107 +744,64 @@ private ConstructorCallSite CreateConstructorCallSite( return parameterCallSites; } - private bool TryResolveConstructorWithoutCreatingArgumentCallSites( - ServiceIdentifier serviceIdentifier, - CallSiteChain callSiteChain, + /// + /// Returns true if every parameter in resolves to a + /// ServiceIdentifier already present in the best constructor's resolved sets. + /// + private static bool ParameterSetIsSubsetOfBest( ParameterInfo[] parameters, + ServiceIdentifier serviceIdentifier, HashSet bestParametersResolvedFromCallSite, - HashSet bestParametersResolvedFromDefault, - out bool hasNewResolvableParameter) + HashSet bestParametersResolvedFromDefault) { - hasNewResolvableParameter = false; - for (int i = 0; i < parameters.Length; i++) { - ParameterInfo parameter = parameters[i]; - Type parameterType = parameter.ParameterType; - ServiceIdentifier parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); - bool isImmediatelyResolved = false; + ServiceIdentifier parameterServiceIdentifier = GetParameterServiceIdentifier(parameters[i], serviceIdentifier); - foreach (object attribute in parameter.GetCustomAttributes(true)) + if (!bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier) && + !bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) { - if (serviceIdentifier.ServiceKey is not null && attribute is ServiceKeyAttribute) - { - if (serviceIdentifier.ServiceKey == KeyedService.AnyKey) - { - parameterType = typeof(object); - } - else if (parameterType != serviceIdentifier.ServiceKey.GetType() && - parameterType != typeof(object)) - { - throw new InvalidOperationException(SR.InvalidServiceKeyType); - } - - parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); - isImmediatelyResolved = true; - break; - } - - if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) - { - object? serviceKey = fromKeyedServicesAttribute.LookupMode switch - { - ServiceKeyLookupMode.InheritKey => serviceIdentifier.ServiceKey, - ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, - ServiceKeyLookupMode.NullKey => null, - _ => null - }; - - if (serviceKey is not null) - { - parameterServiceIdentifier = new ServiceIdentifier(serviceKey, parameterType); - } - - break; - } + return false; } + } - if (isImmediatelyResolved) - { - // [ServiceKey]: always resolvable via constant injection. - if (!bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) - { - hasNewResolvableParameter = true; - } + return true; + } - continue; - } + private static ServiceIdentifier GetParameterServiceIdentifier(ParameterInfo parameter, ServiceIdentifier parentServiceIdentifier) + { + Type parameterType = parameter.ParameterType; - if (bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier)) + foreach (object attribute in parameter.GetCustomAttributes(true)) + { + if (parentServiceIdentifier.ServiceKey is not null && attribute is ServiceKeyAttribute) { - // Container has this service; resolvable and not new. - continue; + Type keyType = parentServiceIdentifier.ServiceKey == KeyedService.AnyKey + ? typeof(object) + : parameterType; + return new ServiceIdentifier(parentServiceIdentifier.ServiceKey, keyType); } - if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) { - // Container doesn't have this (best fell back to its default). - // Resolvable only if this parameter also has a default value. - if (ParameterDefaultValue.TryGetDefaultValue(parameter, out _)) + object? serviceKey = fromKeyedServicesAttribute.LookupMode switch { - continue; - } - - return false; - } + ServiceKeyLookupMode.InheritKey => parentServiceIdentifier.ServiceKey, + ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, + ServiceKeyLookupMode.NullKey => null, + _ => null + }; - // Unknown identity; must probe the container. - if (GetCallSite(parameterServiceIdentifier, callSiteChain) is not null) - { - hasNewResolvableParameter = true; - continue; - } + if (serviceKey is not null) + { + return new ServiceIdentifier(serviceKey, parameterType); + } - if (ParameterDefaultValue.TryGetDefaultValue(parameter, out _)) - { - hasNewResolvableParameter = true; - continue; + break; } - - return false; } - return true; + return ServiceIdentifier.FromServiceType(parameterType); } private bool TryResolveCallSite( From ba86a9318bfa7a91b4e3d8fdb82eb24db776e112 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 09:44:39 +0200 Subject: [PATCH 06/20] remove leftovers --- .../src/ServiceLookup/CallSiteFactory.cs | 96 ++++++------------- 1 file changed, 27 insertions(+), 69 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index e0c645aef66d31..72f3e99bdbb8a6 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -623,7 +623,7 @@ private ConstructorCallSite CreateConstructorCallSite( sortedParameters[sortedIndex] = parameters; } - ConstructorInfo? bestConstructor = null; + ConstructorInfo? bestConstructor = null;how int bestConstructorParameterLength = -1; HashSet? bestParametersResolvedFromCallSite = null; HashSet? bestParametersResolvedFromDefault = null; @@ -662,25 +662,43 @@ private ConstructorCallSite CreateConstructorCallSite( Debug.Assert(bestParametersResolvedFromCallSite is not null); Debug.Assert(bestParametersResolvedFromDefault is not null); + var resolvedFromCallSite = new HashSet(); + var resolvedFromDefault = new HashSet(); if (CreateArgumentCallSites( serviceIdentifier, implementationType, callSiteChain, parameters, - throwIfCallSiteNotFound: false) is null) + throwIfCallSiteNotFound: false, + resolvedFromCallSite, + resolvedFromDefault) is null) { - // Not all parameters are resolvable; skip this constructor. continue; } // All parameters resolvable; check if it's a strict subset of best. - if (!ParameterSetIsSubsetOfBest(parameters, serviceIdentifier, bestParametersResolvedFromCallSite, bestParametersResolvedFromDefault)) + foreach (ServiceIdentifier id in resolvedFromCallSite) { - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructor, - constructor)); + if (!bestParametersResolvedFromCallSite.Contains(id) && !bestParametersResolvedFromDefault.Contains(id)) + { + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructor, + constructor)); + } + } + + foreach (ServiceIdentifier id in resolvedFromDefault) + { + if (!bestParametersResolvedFromCallSite.Contains(id) && !bestParametersResolvedFromDefault.Contains(id)) + { + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructor, + constructor)); + } } } @@ -744,66 +762,6 @@ private ConstructorCallSite CreateConstructorCallSite( return parameterCallSites; } - /// - /// Returns true if every parameter in resolves to a - /// ServiceIdentifier already present in the best constructor's resolved sets. - /// - private static bool ParameterSetIsSubsetOfBest( - ParameterInfo[] parameters, - ServiceIdentifier serviceIdentifier, - HashSet bestParametersResolvedFromCallSite, - HashSet bestParametersResolvedFromDefault) - { - for (int i = 0; i < parameters.Length; i++) - { - ServiceIdentifier parameterServiceIdentifier = GetParameterServiceIdentifier(parameters[i], serviceIdentifier); - - if (!bestParametersResolvedFromCallSite.Contains(parameterServiceIdentifier) && - !bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) - { - return false; - } - } - - return true; - } - - private static ServiceIdentifier GetParameterServiceIdentifier(ParameterInfo parameter, ServiceIdentifier parentServiceIdentifier) - { - Type parameterType = parameter.ParameterType; - - foreach (object attribute in parameter.GetCustomAttributes(true)) - { - if (parentServiceIdentifier.ServiceKey is not null && attribute is ServiceKeyAttribute) - { - Type keyType = parentServiceIdentifier.ServiceKey == KeyedService.AnyKey - ? typeof(object) - : parameterType; - return new ServiceIdentifier(parentServiceIdentifier.ServiceKey, keyType); - } - - if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) - { - object? serviceKey = fromKeyedServicesAttribute.LookupMode switch - { - ServiceKeyLookupMode.InheritKey => parentServiceIdentifier.ServiceKey, - ServiceKeyLookupMode.ExplicitKey => fromKeyedServicesAttribute.Key, - ServiceKeyLookupMode.NullKey => null, - _ => null - }; - - if (serviceKey is not null) - { - return new ServiceIdentifier(serviceKey, parameterType); - } - - break; - } - } - - return ServiceIdentifier.FromServiceType(parameterType); - } - private bool TryResolveCallSite( ServiceIdentifier serviceIdentifier, Type implementationType, From c8386bcc7a157c6f7eda17a74693698f74316531 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 09:59:37 +0200 Subject: [PATCH 07/20] cleanup --- .../src/ServiceLookup/CallSiteFactory.cs | 69 ++++--------------- 1 file changed, 12 insertions(+), 57 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 72f3e99bdbb8a6..c9519c8ddce0ae 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -623,10 +623,8 @@ private ConstructorCallSite CreateConstructorCallSite( sortedParameters[sortedIndex] = parameters; } - ConstructorInfo? bestConstructor = null;how - int bestConstructorParameterLength = -1; - HashSet? bestParametersResolvedFromCallSite = null; - HashSet? bestParametersResolvedFromDefault = null; + ConstructorInfo? bestConstructor = null; + HashSet? bestResolvedParameters = null; for (int i = 0; i < constructorCount; i++) { @@ -635,16 +633,14 @@ private ConstructorCallSite CreateConstructorCallSite( if (bestConstructor is null) { - var currentParametersResolvedFromCallSite = new HashSet(); - var currentParametersResolvedFromDefault = new HashSet(); + var currentResolvedParameters = new HashSet(); ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, implementationType, callSiteChain, parameters, throwIfCallSiteNotFound: false, - currentParametersResolvedFromCallSite, - currentParametersResolvedFromDefault); + currentResolvedParameters); if (currentParameterCallSites is null) { @@ -652,46 +648,29 @@ private ConstructorCallSite CreateConstructorCallSite( } bestConstructor = constructor; - bestConstructorParameterLength = parameters.Length; parameterCallSites = currentParameterCallSites; - bestParametersResolvedFromCallSite = currentParametersResolvedFromCallSite; - bestParametersResolvedFromDefault = currentParametersResolvedFromDefault; + bestResolvedParameters = currentResolvedParameters; continue; } - Debug.Assert(bestParametersResolvedFromCallSite is not null); - Debug.Assert(bestParametersResolvedFromDefault is not null); + Debug.Assert(bestResolvedParameters is not null); - var resolvedFromCallSite = new HashSet(); - var resolvedFromDefault = new HashSet(); + var resolvedParameters = new HashSet(); if (CreateArgumentCallSites( serviceIdentifier, implementationType, callSiteChain, parameters, throwIfCallSiteNotFound: false, - resolvedFromCallSite, - resolvedFromDefault) is null) + resolvedParameters) is null) { continue; } // All parameters resolvable; check if it's a strict subset of best. - foreach (ServiceIdentifier id in resolvedFromCallSite) + foreach (ServiceIdentifier id in resolvedParameters) { - if (!bestParametersResolvedFromCallSite.Contains(id) && !bestParametersResolvedFromDefault.Contains(id)) - { - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructor, - constructor)); - } - } - - foreach (ServiceIdentifier id in resolvedFromDefault) - { - if (!bestParametersResolvedFromCallSite.Contains(id) && !bestParametersResolvedFromDefault.Contains(id)) + if (!bestResolvedParameters.Contains(id)) { throw new InvalidOperationException(string.Join( Environment.NewLine, @@ -709,7 +688,6 @@ private ConstructorCallSite CreateConstructorCallSite( } Debug.Assert(parameterCallSites != null); - Debug.Assert(bestConstructorParameterLength >= 0); return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, bestConstructor, parameterCallSites, serviceIdentifier.ServiceKey); } finally @@ -725,8 +703,7 @@ private ConstructorCallSite CreateConstructorCallSite( CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound, - HashSet? parametersResolvedFromCallSite = null, - HashSet? parametersResolvedFromDefault = null) + HashSet? resolvedParameters = null) { var parameterCallSites = new ServiceCallSite[parameters.Length]; @@ -739,23 +716,13 @@ private ConstructorCallSite CreateConstructorCallSite( parameters[index], throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ParameterResolutionKind parameterResolutionKind, out ServiceCallSite? callSite)) { return null; } Debug.Assert(callSite is not null); - if (parameterResolutionKind == ParameterResolutionKind.FromCallSite) - { - parametersResolvedFromCallSite?.Add(parameterServiceIdentifier); - } - else - { - Debug.Assert(parameterResolutionKind == ParameterResolutionKind.FromDefaultValue); - parametersResolvedFromDefault?.Add(parameterServiceIdentifier); - } - + resolvedParameters?.Add(parameterServiceIdentifier); parameterCallSites[index] = callSite; } @@ -769,7 +736,6 @@ private bool TryResolveCallSite( ParameterInfo parameter, bool throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ParameterResolutionKind parameterResolutionKind, out ServiceCallSite? callSite) { Type parameterType = parameter.ParameterType; @@ -791,7 +757,6 @@ private bool TryResolveCallSite( } parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); - parameterResolutionKind = ParameterResolutionKind.FromCallSite; callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); return true; } @@ -816,14 +781,12 @@ private bool TryResolveCallSite( ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); if (parameterCallSite is not null) { - parameterResolutionKind = ParameterResolutionKind.FromCallSite; callSite = parameterCallSite; return true; } if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) { - parameterResolutionKind = ParameterResolutionKind.FromDefaultValue; callSite = new ConstantCallSite(parameterType, defaultValue); return true; } @@ -835,18 +798,10 @@ private bool TryResolveCallSite( implementationType)); } - parameterResolutionKind = ParameterResolutionKind.Unresolved; callSite = null; return false; } - private enum ParameterResolutionKind - { - Unresolved, - FromCallSite, - FromDefaultValue, - } - /// /// Verifies none of the generic type arguments are ValueTypes. /// From 75aeb9a38821882706064987574d5bce8b64b5bf Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 10:42:28 +0200 Subject: [PATCH 08/20] use lists --- .../src/ServiceLookup/CallSiteFactory.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index c9519c8ddce0ae..97e3851e8d73b8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -624,7 +624,7 @@ private ConstructorCallSite CreateConstructorCallSite( } ConstructorInfo? bestConstructor = null; - HashSet? bestResolvedParameters = null; + List? bestResolvedParameters = null; for (int i = 0; i < constructorCount; i++) { @@ -633,7 +633,7 @@ private ConstructorCallSite CreateConstructorCallSite( if (bestConstructor is null) { - var currentResolvedParameters = new HashSet(); + var currentResolvedParameters = new List(); ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, implementationType, @@ -655,7 +655,7 @@ private ConstructorCallSite CreateConstructorCallSite( Debug.Assert(bestResolvedParameters is not null); - var resolvedParameters = new HashSet(); + var resolvedParameters = new List(); if (CreateArgumentCallSites( serviceIdentifier, implementationType, @@ -703,7 +703,7 @@ private ConstructorCallSite CreateConstructorCallSite( CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound, - HashSet? resolvedParameters = null) + List? resolvedParameters = null) { var parameterCallSites = new ServiceCallSite[parameters.Length]; From 1572db93cb643011df982e83db4d79243f9411cb Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 11:50:40 +0200 Subject: [PATCH 09/20] point --- .../src/ServiceLookup/CallSiteFactory.cs | 3 ++- .../tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 97e3851e8d73b8..36e43edbde400b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -625,6 +625,7 @@ private ConstructorCallSite CreateConstructorCallSite( ConstructorInfo? bestConstructor = null; List? bestResolvedParameters = null; + var resolvedParameters = new List(); for (int i = 0; i < constructorCount; i++) { @@ -655,7 +656,7 @@ private ConstructorCallSite CreateConstructorCallSite( Debug.Assert(bestResolvedParameters is not null); - var resolvedParameters = new List(); + resolvedParameters.Clear(); if (CreateArgumentCallSites( serviceIdentifier, implementationType, diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index f1f262bd9f9021..18f0b7a1c3f970 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -209,8 +209,7 @@ public void CreateCallSite_ThrowsIfSameTypeParametersUseDifferentServiceKeys() new ServiceDescriptor(type, type, ServiceLifetime.Transient), ServiceDescriptor.KeyedTransient("a"), ServiceDescriptor.KeyedTransient("b"), - new ServiceDescriptor(typeof(IFakeScopedService), typeof(FakeService), ServiceLifetime.Transient), - new ServiceDescriptor(typeof(IFakeMultipleService), typeof(FakeService), ServiceLifetime.Transient)); + new ServiceDescriptor(typeof(IFakeScopedService), typeof(FakeService), ServiceLifetime.Transient)); // Act and Assert var ex = Assert.Throws(() => callSiteFactory(type)); @@ -1219,7 +1218,7 @@ public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("a")] { } - public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("b")] IFakeService service, IFakeMultipleService multiple) + public TypeWithSameTypeDifferentServiceKeyConstructors([FromKeyedServices("b")] IFakeService service, IFakeScopedService scoped, int dummy = 0) { } } From da0e3de904fe47d2a326fcaade8b41b5738c612b Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 12:55:03 +0200 Subject: [PATCH 10/20] presize lists --- .../src/ServiceLookup/CallSiteFactory.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 36e43edbde400b..a5ed444188f037 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -625,7 +625,7 @@ private ConstructorCallSite CreateConstructorCallSite( ConstructorInfo? bestConstructor = null; List? bestResolvedParameters = null; - var resolvedParameters = new List(); + List? resolvedParameters = null; for (int i = 0; i < constructorCount; i++) { @@ -634,7 +634,7 @@ private ConstructorCallSite CreateConstructorCallSite( if (bestConstructor is null) { - var currentResolvedParameters = new List(); + var currentResolvedParameters = new List(parameters.Length); ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, implementationType, @@ -656,7 +656,15 @@ private ConstructorCallSite CreateConstructorCallSite( Debug.Assert(bestResolvedParameters is not null); - resolvedParameters.Clear(); + if (resolvedParameters is null) + { + resolvedParameters = new List(parameters.Length); + } + else + { + resolvedParameters.Clear(); + } + if (CreateArgumentCallSites( serviceIdentifier, implementationType, From ce746f99e67db61ddc805741d3f6597ebf23f40a Mon Sep 17 00:00:00 2001 From: Jaroslav Ruzicka <14963300+rosebyte@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:04:17 +0200 Subject: [PATCH 11/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 18f0b7a1c3f970..74f8179f91b98c 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -226,7 +226,7 @@ public void CreateCallSite_IgnoresSmallerConstructorWhenBestUsesDefaultAndSmalle new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient)); // Act - var callSite = (ServiceCallSite)callSiteFactory(type); +var callSite = callSiteFactory(type); // Assert var constructorCallSite = Assert.IsType(callSite); From 55cd4fc412206837700cb52f77ab0fb656d324ed Mon Sep 17 00:00:00 2001 From: Jaroslav Ruzicka <14963300+rosebyte@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:37:28 +0200 Subject: [PATCH 12/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index a5ed444188f037..f68847f3334a95 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -676,7 +676,7 @@ private ConstructorCallSite CreateConstructorCallSite( continue; } - // All parameters resolvable; check if it's a strict subset of best. + // All parameters resolvable; check if it's a subset of best. foreach (ServiceIdentifier id in resolvedParameters) { if (!bestResolvedParameters.Contains(id)) From f113ed00ecaa98e900bcb2f02e5dd1d79d83431d Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 22:59:31 +0200 Subject: [PATCH 13/20] fix indentation --- .../tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 74f8179f91b98c..546200f30b7700 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -226,7 +226,7 @@ public void CreateCallSite_IgnoresSmallerConstructorWhenBestUsesDefaultAndSmalle new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient)); // Act -var callSite = callSiteFactory(type); + var callSite = callSiteFactory(type); // Assert var constructorCallSite = Assert.IsType(callSite); From eecbd927e1759c561d695a9788cfc52ac330555f Mon Sep 17 00:00:00 2001 From: Jaroslav Ruzicka <14963300+rosebyte@users.noreply.github.com> Date: Tue, 16 Jun 2026 23:33:46 +0200 Subject: [PATCH 14/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index f68847f3334a95..78b009da2b305d 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -629,8 +629,8 @@ private ConstructorCallSite CreateConstructorCallSite( for (int i = 0; i < constructorCount; i++) { - ConstructorInfo constructor = sortedConstructors[i]; - ParameterInfo[] parameters = sortedParameters[i]; + ConstructorInfo constructor = sortedConstructors[i]!; + ParameterInfo[] parameters = sortedParameters[i]!; if (bestConstructor is null) { From 4affbdacc41007e883c47d69d642e883609a2310 Mon Sep 17 00:00:00 2001 From: rosebyte Date: Tue, 16 Jun 2026 23:43:16 +0200 Subject: [PATCH 15/20] fix copilot hallucination --- .../src/ServiceLookup/CallSiteFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 78b009da2b305d..f68847f3334a95 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -629,8 +629,8 @@ private ConstructorCallSite CreateConstructorCallSite( for (int i = 0; i < constructorCount; i++) { - ConstructorInfo constructor = sortedConstructors[i]!; - ParameterInfo[] parameters = sortedParameters[i]!; + ConstructorInfo constructor = sortedConstructors[i]; + ParameterInfo[] parameters = sortedParameters[i]; if (bestConstructor is null) { From e8c3db1030ed703fef14717c4f05610b12669589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:10:09 +0000 Subject: [PATCH 16/20] Fix DI default-parameter constructor probing Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 229 +++++++++++++----- .../ServiceLookup/CallSiteFactoryTest.cs | 24 +- 2 files changed, 190 insertions(+), 63 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index f68847f3334a95..129ca83a640dc8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -612,10 +612,10 @@ private ConstructorCallSite CreateConstructorCallSite( ParameterInfo[] parameters = constructor.GetParameters(); int sortedIndex = i; - while (sortedIndex > 0 && sortedParameters[sortedIndex - 1].Length < parameters.Length) + while (sortedIndex > 0 && sortedParameters[sortedIndex - 1]!.Length < parameters.Length) { - sortedConstructors[sortedIndex] = sortedConstructors[sortedIndex - 1]; - sortedParameters[sortedIndex] = sortedParameters[sortedIndex - 1]; + sortedConstructors[sortedIndex] = sortedConstructors[sortedIndex - 1]!; + sortedParameters[sortedIndex] = sortedParameters[sortedIndex - 1]!; sortedIndex--; } @@ -624,17 +624,17 @@ private ConstructorCallSite CreateConstructorCallSite( } ConstructorInfo? bestConstructor = null; - List? bestResolvedParameters = null; - List? resolvedParameters = null; + HashSet? bestParametersResolvedFromContainer = null; + HashSet? bestParametersResolvedFromDefault = null; for (int i = 0; i < constructorCount; i++) { - ConstructorInfo constructor = sortedConstructors[i]; - ParameterInfo[] parameters = sortedParameters[i]; + ConstructorInfo constructor = sortedConstructors[i]!; + ParameterInfo[] parameters = sortedParameters[i]!; if (bestConstructor is null) { - var currentResolvedParameters = new List(parameters.Length); + var currentResolvedParameters = new List(parameters.Length); ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, implementationType, @@ -650,43 +650,36 @@ private ConstructorCallSite CreateConstructorCallSite( bestConstructor = constructor; parameterCallSites = currentParameterCallSites; - bestResolvedParameters = currentResolvedParameters; + ClassifyResolvedParameters( + currentResolvedParameters, + out bestParametersResolvedFromContainer, + out bestParametersResolvedFromDefault); continue; } - Debug.Assert(bestResolvedParameters is not null); + Debug.Assert(bestParametersResolvedFromContainer is not null); + Debug.Assert(bestParametersResolvedFromDefault is not null); - if (resolvedParameters is null) - { - resolvedParameters = new List(parameters.Length); - } - else - { - resolvedParameters.Clear(); - } - - if (CreateArgumentCallSites( + if (!IsResolvableWithoutCreatingCallSiteArray( serviceIdentifier, implementationType, callSiteChain, parameters, throwIfCallSiteNotFound: false, - resolvedParameters) is null) + bestParametersResolvedFromContainer, + bestParametersResolvedFromDefault, + out bool hasNewResolvableParameter)) { continue; } - // All parameters resolvable; check if it's a subset of best. - foreach (ServiceIdentifier id in resolvedParameters) + if (hasNewResolvableParameter) { - if (!bestResolvedParameters.Contains(id)) - { - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructor, - constructor)); - } + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructor, + constructor)); } } @@ -712,7 +705,7 @@ private ConstructorCallSite CreateConstructorCallSite( CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound, - List? resolvedParameters = null) + List? resolvedParameters = null) { var parameterCallSites = new ServiceCallSite[parameters.Length]; @@ -725,19 +718,102 @@ private ConstructorCallSite CreateConstructorCallSite( parameters[index], throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ServiceCallSite? callSite)) + out ServiceCallSite? callSite, + out bool resolvedFromDefault)) { return null; } Debug.Assert(callSite is not null); - resolvedParameters?.Add(parameterServiceIdentifier); + resolvedParameters?.Add(new ResolvedParameter(parameterServiceIdentifier, resolvedFromDefault)); parameterCallSites[index] = callSite; } return parameterCallSites; } + private static void ClassifyResolvedParameters( + List resolvedParameters, + out HashSet resolvedFromContainer, + out HashSet resolvedFromDefault) + { + resolvedFromContainer = new HashSet(); + resolvedFromDefault = new HashSet(); + + foreach (ResolvedParameter resolvedParameter in resolvedParameters) + { + if (resolvedParameter.ResolvedFromDefault) + { + resolvedFromDefault.Add(resolvedParameter.ServiceIdentifier); + } + else + { + resolvedFromContainer.Add(resolvedParameter.ServiceIdentifier); + } + } + } + + private bool IsResolvableWithoutCreatingCallSiteArray( + ServiceIdentifier serviceIdentifier, + Type implementationType, + CallSiteChain callSiteChain, + ParameterInfo[] parameters, + bool throwIfCallSiteNotFound, + HashSet bestParametersResolvedFromContainer, + HashSet bestParametersResolvedFromDefault, + out bool hasNewResolvableParameter) + { + hasNewResolvableParameter = false; + + for (int i = 0; i < parameters.Length; i++) + { + ParameterInfo parameter = parameters[i]; + ServiceIdentifier parameterServiceIdentifier = GetParameterServiceIdentifier( + serviceIdentifier, + parameter, + out _, + out _); + + if (bestParametersResolvedFromContainer.Contains(parameterServiceIdentifier)) + { + continue; + } + + bool defaultMatchesBestParameter = + bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier) && + ParameterDefaultValue.TryGetDefaultValue(parameter, out _); + if (defaultMatchesBestParameter) + { + continue; + } + + if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + { + return false; + } + + if (!TryResolveCallSite( + serviceIdentifier, + implementationType, + callSiteChain, + parameter, + throwIfCallSiteNotFound, + out parameterServiceIdentifier, + out _, + out bool resolvedFromDefault)) + { + return false; + } + + if (!resolvedFromDefault || !bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) + { + hasNewResolvableParameter = true; + } + } + + return true; + } + private bool TryResolveCallSite( ServiceIdentifier serviceIdentifier, Type implementationType, @@ -745,10 +821,58 @@ private bool TryResolveCallSite( ParameterInfo parameter, bool throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ServiceCallSite? callSite) + out ServiceCallSite? callSite, + out bool resolvedFromDefault) { - Type parameterType = parameter.ParameterType; - parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); + parameterServiceIdentifier = GetParameterServiceIdentifier( + serviceIdentifier, + parameter, + out Type parameterType, + out bool serviceKeyParameter); + + if (serviceKeyParameter) + { + resolvedFromDefault = false; + callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); + return true; + } + + ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); + if (parameterCallSite is not null) + { + resolvedFromDefault = false; + callSite = parameterCallSite; + return true; + } + + if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) + { + resolvedFromDefault = true; + callSite = new ConstantCallSite(parameterType, defaultValue); + return true; + } + + if (throwIfCallSiteNotFound) + { + throw new InvalidOperationException(SR.Format(SR.CannotResolveService, + parameterType, + implementationType)); + } + + resolvedFromDefault = false; + callSite = null; + return false; + } + + private static ServiceIdentifier GetParameterServiceIdentifier( + ServiceIdentifier serviceIdentifier, + ParameterInfo parameter, + out Type parameterType, + out bool serviceKeyParameter) + { + parameterType = parameter.ParameterType; + ServiceIdentifier parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); + serviceKeyParameter = false; foreach (object attribute in parameter.GetCustomAttributes(true)) { @@ -765,9 +889,8 @@ private bool TryResolveCallSite( throw new InvalidOperationException(SR.InvalidServiceKeyType); } - parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); - callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); - return true; + serviceKeyParameter = true; + return new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); } if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) @@ -787,28 +910,20 @@ private bool TryResolveCallSite( } } - ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); - if (parameterCallSite is not null) - { - callSite = parameterCallSite; - return true; - } + return parameterServiceIdentifier; + } - if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) + private readonly struct ResolvedParameter + { + public ResolvedParameter(ServiceIdentifier serviceIdentifier, bool resolvedFromDefault) { - callSite = new ConstantCallSite(parameterType, defaultValue); - return true; + ServiceIdentifier = serviceIdentifier; + ResolvedFromDefault = resolvedFromDefault; } - if (throwIfCallSiteNotFound) - { - throw new InvalidOperationException(SR.Format(SR.CannotResolveService, - parameterType, - implementationType)); - } + public ServiceIdentifier ServiceIdentifier { get; } - callSite = null; - return false; + public bool ResolvedFromDefault { get; } } /// diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs index 546200f30b7700..30045000c0c345 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceLookup/CallSiteFactoryTest.cs @@ -216,14 +216,26 @@ public void CreateCallSite_ThrowsIfSameTypeParametersUseDifferentServiceKeys() Assert.StartsWith($"Unable to activate type '{type}'. The following constructors are ambiguous:", ex.Message); } - [Fact] - public void CreateCallSite_IgnoresSmallerConstructorWhenBestUsesDefaultAndSmallerNeedsContainer() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CreateCallSite_IgnoresSmallerConstructorWhenBestUsesDefaultAndSmallerNeedsContainer(bool registerScopedService) { // Arrange var type = typeof(TypeWithDefaultInBestAndNonDefaultInSmallerConstructors); - var callSiteFactory = GetCallSiteFactory( - new ServiceDescriptor(type, type, ServiceLifetime.Transient), - new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient)); + ServiceDescriptor[] descriptors = registerScopedService + ? new[] + { + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeScopedService), typeof(FakeService), ServiceLifetime.Transient), + } + : new[] + { + new ServiceDescriptor(type, type, ServiceLifetime.Transient), + new ServiceDescriptor(typeof(IFakeService), typeof(FakeService), ServiceLifetime.Transient), + }; + var callSiteFactory = GetCallSiteFactory(descriptors); // Act var callSite = callSiteFactory(type); @@ -250,7 +262,7 @@ public void CreateCallSite_UsesLongestResolvableConstructorWhenDeclaredAfterShor : new[] { typeof(IFakeService) }; // Act - var callSite = (ServiceCallSite)callSiteFactory(type); + var callSite = callSiteFactory(type); // Assert Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location); From faf2f72446266a214906a6ba9ef1561f9a81b49e Mon Sep 17 00:00:00 2001 From: rosebyte Date: Thu, 18 Jun 2026 12:38:18 +0200 Subject: [PATCH 17/20] implement PR comments, reintroduce refactor --- .../src/ServiceLookup/CallSiteFactory.cs | 244 +++++------------- 1 file changed, 70 insertions(+), 174 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 129ca83a640dc8..760f44b3aa98aa 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -603,8 +603,19 @@ private ConstructorCallSite CreateConstructorCallSite( return new ConstructorCallSite(lifetime, serviceIdentifier.ServiceType, constructor, parameterCallSites, serviceIdentifier.ServiceKey); } + // With more than one constructor, select the "best" one: the constructor with the + // most parameters whose arguments can all be resolved, either from the container or + // by falling back to a default parameter value. Constructors are ordered from the + // most parameters to the fewest (preserving declaration order among equal counts), + // and the first fully resolvable one becomes the best match. Every subsequent + // resolvable constructor must be a strict subset of the best one, i.e. each of its + // parameters was already used by the best constructor; otherwise the two are + // ambiguous and an exception is thrown. + // + // The constructors array returned by GetConstructors() is private to this method, so + // it is sorted in place via an insertion sort. The parameter arrays are cached + // alongside it to avoid calling GetParameters() again during selection. int constructorCount = constructors.Length; - var sortedConstructors = new ConstructorInfo[constructorCount]; var sortedParameters = new ParameterInfo[constructorCount][]; for (int i = 0; i < constructorCount; i++) { @@ -612,29 +623,29 @@ private ConstructorCallSite CreateConstructorCallSite( ParameterInfo[] parameters = constructor.GetParameters(); int sortedIndex = i; - while (sortedIndex > 0 && sortedParameters[sortedIndex - 1]!.Length < parameters.Length) + while (sortedIndex > 0 && sortedParameters[sortedIndex - 1].Length < parameters.Length) { - sortedConstructors[sortedIndex] = sortedConstructors[sortedIndex - 1]!; - sortedParameters[sortedIndex] = sortedParameters[sortedIndex - 1]!; + constructors[sortedIndex] = constructors[sortedIndex - 1]; + sortedParameters[sortedIndex] = sortedParameters[sortedIndex - 1]; sortedIndex--; } - sortedConstructors[sortedIndex] = constructor; + constructors[sortedIndex] = constructor; sortedParameters[sortedIndex] = parameters; } ConstructorInfo? bestConstructor = null; - HashSet? bestParametersResolvedFromContainer = null; - HashSet? bestParametersResolvedFromDefault = null; + List? bestResolvedParameters = null; + List? resolvedParameters = null; for (int i = 0; i < constructorCount; i++) { - ConstructorInfo constructor = sortedConstructors[i]!; - ParameterInfo[] parameters = sortedParameters[i]!; + ConstructorInfo constructor = constructors[i]; + ParameterInfo[] parameters = sortedParameters[i]; if (bestConstructor is null) { - var currentResolvedParameters = new List(parameters.Length); + var currentResolvedParameters = new List(parameters.Length); ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites( serviceIdentifier, implementationType, @@ -650,36 +661,43 @@ private ConstructorCallSite CreateConstructorCallSite( bestConstructor = constructor; parameterCallSites = currentParameterCallSites; - ClassifyResolvedParameters( - currentResolvedParameters, - out bestParametersResolvedFromContainer, - out bestParametersResolvedFromDefault); + bestResolvedParameters = currentResolvedParameters; continue; } - Debug.Assert(bestParametersResolvedFromContainer is not null); - Debug.Assert(bestParametersResolvedFromDefault is not null); + Debug.Assert(bestResolvedParameters is not null); - if (!IsResolvableWithoutCreatingCallSiteArray( + if (resolvedParameters is null) + { + resolvedParameters = new List(parameters.Length); + } + else + { + resolvedParameters.Clear(); + } + + if (CreateArgumentCallSites( serviceIdentifier, implementationType, callSiteChain, parameters, throwIfCallSiteNotFound: false, - bestParametersResolvedFromContainer, - bestParametersResolvedFromDefault, - out bool hasNewResolvableParameter)) + resolvedParameters) is null) { continue; } - if (hasNewResolvableParameter) + // All parameters resolvable; ambiguous unless it is a strict subset of best. + foreach (ServiceIdentifier id in resolvedParameters) { - throw new InvalidOperationException(string.Join( - Environment.NewLine, - SR.Format(SR.AmbiguousConstructorException, implementationType), - bestConstructor, - constructor)); + if (!bestResolvedParameters.Contains(id)) + { + throw new InvalidOperationException(string.Join( + Environment.NewLine, + SR.Format(SR.AmbiguousConstructorException, implementationType), + bestConstructor, + constructor)); + } } } @@ -705,7 +723,7 @@ private ConstructorCallSite CreateConstructorCallSite( CallSiteChain callSiteChain, ParameterInfo[] parameters, bool throwIfCallSiteNotFound, - List? resolvedParameters = null) + List? resolvedParameters = null) { var parameterCallSites = new ServiceCallSite[parameters.Length]; @@ -718,102 +736,19 @@ private ConstructorCallSite CreateConstructorCallSite( parameters[index], throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ServiceCallSite? callSite, - out bool resolvedFromDefault)) + out ServiceCallSite? callSite)) { return null; } Debug.Assert(callSite is not null); - resolvedParameters?.Add(new ResolvedParameter(parameterServiceIdentifier, resolvedFromDefault)); + resolvedParameters?.Add(parameterServiceIdentifier); parameterCallSites[index] = callSite; } return parameterCallSites; } - private static void ClassifyResolvedParameters( - List resolvedParameters, - out HashSet resolvedFromContainer, - out HashSet resolvedFromDefault) - { - resolvedFromContainer = new HashSet(); - resolvedFromDefault = new HashSet(); - - foreach (ResolvedParameter resolvedParameter in resolvedParameters) - { - if (resolvedParameter.ResolvedFromDefault) - { - resolvedFromDefault.Add(resolvedParameter.ServiceIdentifier); - } - else - { - resolvedFromContainer.Add(resolvedParameter.ServiceIdentifier); - } - } - } - - private bool IsResolvableWithoutCreatingCallSiteArray( - ServiceIdentifier serviceIdentifier, - Type implementationType, - CallSiteChain callSiteChain, - ParameterInfo[] parameters, - bool throwIfCallSiteNotFound, - HashSet bestParametersResolvedFromContainer, - HashSet bestParametersResolvedFromDefault, - out bool hasNewResolvableParameter) - { - hasNewResolvableParameter = false; - - for (int i = 0; i < parameters.Length; i++) - { - ParameterInfo parameter = parameters[i]; - ServiceIdentifier parameterServiceIdentifier = GetParameterServiceIdentifier( - serviceIdentifier, - parameter, - out _, - out _); - - if (bestParametersResolvedFromContainer.Contains(parameterServiceIdentifier)) - { - continue; - } - - bool defaultMatchesBestParameter = - bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier) && - ParameterDefaultValue.TryGetDefaultValue(parameter, out _); - if (defaultMatchesBestParameter) - { - continue; - } - - if (bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) - { - return false; - } - - if (!TryResolveCallSite( - serviceIdentifier, - implementationType, - callSiteChain, - parameter, - throwIfCallSiteNotFound, - out parameterServiceIdentifier, - out _, - out bool resolvedFromDefault)) - { - return false; - } - - if (!resolvedFromDefault || !bestParametersResolvedFromDefault.Contains(parameterServiceIdentifier)) - { - hasNewResolvableParameter = true; - } - } - - return true; - } - private bool TryResolveCallSite( ServiceIdentifier serviceIdentifier, Type implementationType, @@ -821,58 +756,10 @@ private bool TryResolveCallSite( ParameterInfo parameter, bool throwIfCallSiteNotFound, out ServiceIdentifier parameterServiceIdentifier, - out ServiceCallSite? callSite, - out bool resolvedFromDefault) + out ServiceCallSite? callSite) { - parameterServiceIdentifier = GetParameterServiceIdentifier( - serviceIdentifier, - parameter, - out Type parameterType, - out bool serviceKeyParameter); - - if (serviceKeyParameter) - { - resolvedFromDefault = false; - callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); - return true; - } - - ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); - if (parameterCallSite is not null) - { - resolvedFromDefault = false; - callSite = parameterCallSite; - return true; - } - - if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) - { - resolvedFromDefault = true; - callSite = new ConstantCallSite(parameterType, defaultValue); - return true; - } - - if (throwIfCallSiteNotFound) - { - throw new InvalidOperationException(SR.Format(SR.CannotResolveService, - parameterType, - implementationType)); - } - - resolvedFromDefault = false; - callSite = null; - return false; - } - - private static ServiceIdentifier GetParameterServiceIdentifier( - ServiceIdentifier serviceIdentifier, - ParameterInfo parameter, - out Type parameterType, - out bool serviceKeyParameter) - { - parameterType = parameter.ParameterType; - ServiceIdentifier parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); - serviceKeyParameter = false; + Type parameterType = parameter.ParameterType; + parameterServiceIdentifier = ServiceIdentifier.FromServiceType(parameterType); foreach (object attribute in parameter.GetCustomAttributes(true)) { @@ -889,8 +776,9 @@ private static ServiceIdentifier GetParameterServiceIdentifier( throw new InvalidOperationException(SR.InvalidServiceKeyType); } - serviceKeyParameter = true; - return new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); + parameterServiceIdentifier = new ServiceIdentifier(serviceIdentifier.ServiceKey, parameterType); + callSite = new ConstantCallSite(parameterType, serviceIdentifier.ServiceKey); + return true; } if (attribute is FromKeyedServicesAttribute fromKeyedServicesAttribute) @@ -910,20 +798,28 @@ private static ServiceIdentifier GetParameterServiceIdentifier( } } - return parameterServiceIdentifier; - } + ServiceCallSite? parameterCallSite = GetCallSite(parameterServiceIdentifier, callSiteChain); + if (parameterCallSite is not null) + { + callSite = parameterCallSite; + return true; + } - private readonly struct ResolvedParameter - { - public ResolvedParameter(ServiceIdentifier serviceIdentifier, bool resolvedFromDefault) + if (ParameterDefaultValue.TryGetDefaultValue(parameter, out object? defaultValue)) { - ServiceIdentifier = serviceIdentifier; - ResolvedFromDefault = resolvedFromDefault; + callSite = new ConstantCallSite(parameterType, defaultValue); + return true; } - public ServiceIdentifier ServiceIdentifier { get; } + if (throwIfCallSiteNotFound) + { + throw new InvalidOperationException(SR.Format(SR.CannotResolveService, + parameterType, + implementationType)); + } - public bool ResolvedFromDefault { get; } + callSite = null; + return false; } /// From 2e9ac81840819bd88d0f26e76af25cf645a3383f Mon Sep 17 00:00:00 2001 From: rosebyte Date: Thu, 18 Jun 2026 13:30:24 +0200 Subject: [PATCH 18/20] improve comment. --- .../src/ServiceLookup/CallSiteFactory.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 760f44b3aa98aa..b867e698008f84 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -611,10 +611,6 @@ private ConstructorCallSite CreateConstructorCallSite( // resolvable constructor must be a strict subset of the best one, i.e. each of its // parameters was already used by the best constructor; otherwise the two are // ambiguous and an exception is thrown. - // - // The constructors array returned by GetConstructors() is private to this method, so - // it is sorted in place via an insertion sort. The parameter arrays are cached - // alongside it to avoid calling GetParameters() again during selection. int constructorCount = constructors.Length; var sortedParameters = new ParameterInfo[constructorCount][]; for (int i = 0; i < constructorCount; i++) From 754dcb677b58120e7aff48084896c9b797bd9998 Mon Sep 17 00:00:00 2001 From: Jaroslav Ruzicka <14963300+rosebyte@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:29:38 +0200 Subject: [PATCH 19/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index b867e698008f84..ad44ce2f7d9f34 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -608,9 +608,9 @@ private ConstructorCallSite CreateConstructorCallSite( // by falling back to a default parameter value. Constructors are ordered from the // most parameters to the fewest (preserving declaration order among equal counts), // and the first fully resolvable one becomes the best match. Every subsequent - // resolvable constructor must be a strict subset of the best one, i.e. each of its - // parameters was already used by the best constructor; otherwise the two are - // ambiguous and an exception is thrown. + // resolvable constructor must have parameters that are a subset of the best one, + // i.e. each of its parameters was already used by the best constructor; otherwise + // the two are ambiguous and an exception is thrown. int constructorCount = constructors.Length; var sortedParameters = new ParameterInfo[constructorCount][]; for (int i = 0; i < constructorCount; i++) From c8a0dea52495b995ed77d75cdf7fc1529ed585ee Mon Sep 17 00:00:00 2001 From: Jaroslav Ruzicka <14963300+rosebyte@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:29:57 +0200 Subject: [PATCH 20/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/ServiceLookup/CallSiteFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index ad44ce2f7d9f34..ad8d811545bd91 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -683,7 +683,7 @@ private ConstructorCallSite CreateConstructorCallSite( continue; } - // All parameters resolvable; ambiguous unless it is a strict subset of best. + // All parameters resolvable; ambiguous unless it is a subset of best. foreach (ServiceIdentifier id in resolvedParameters) { if (!bestResolvedParameters.Contains(id))