From bb919c24f8e79c626f34521861974fce271e7641 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 6 Mar 2024 08:58:51 -0600 Subject: [PATCH] ActivatorUtilities.CreateInstance() should respect [ActivatorUtilitiesConstructor] (#99175) --- .../src/ActivatorUtilities.cs | 19 +- .../tests/DI.Tests/ActivatorUtilitiesTests.cs | 192 ++++++++++++++++++ 2 files changed, 204 insertions(+), 7 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs index e8eae2d2e9eee..8c8fcbd5f18b4 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs @@ -103,20 +103,25 @@ public static object CreateInstance( { ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); } - } - if (isPreferred || bestLength < length) - { + seenPreferred = true; bestLength = length; bestMatcher = matcher; multipleBestLengthFound = false; } - else if (bestLength == length) + else if (!seenPreferred) { - multipleBestLengthFound = true; + if (bestLength < length) + { + bestLength = length; + bestMatcher = matcher; + multipleBestLengthFound = false; + } + else if (bestLength == length) + { + multipleBestLengthFound = true; + } } - - seenPreferred |= isPreferred; } if (bestLength != -1) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs index f6e7c2f3a8eb7..1144ff33a1c7b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs @@ -170,6 +170,96 @@ public void CreateInstance_ClassWithABC_ConstructorWithAttribute_PicksCtorWithAt Assert.Same(a, instance.A); } + [Fact] + public void CreateInstanceFailsWithAmbiguousConstructor() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + // Neither ctor(A) nor ctor(B) have [ActivatorUtilitiesConstructor]. + Assert.Throws(() => ActivatorUtilities.CreateInstance(serviceProvider)); + } + + [Fact] + public void CreateInstanceFailsWithAmbiguousConstructor_ReversedOrder() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + // Neither ctor(A) nor ctor(B) have [ActivatorUtilitiesConstructor]. + Assert.Throws(() => ActivatorUtilities.CreateInstance(serviceProvider)); + } + + [Fact] + public void CreateInstancePassesWithAmbiguousConstructor() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var service = ActivatorUtilities.CreateInstance(serviceProvider); + + // Ensure ctor(A) was selected over ctor(B) since A has [ActivatorUtilitiesConstructor]. + Assert.NotNull(service.A); + } + + [Fact] + public void CreateInstancePassesWithAmbiguousConstructor_ReversedOrder() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var service = ActivatorUtilities.CreateInstance(serviceProvider); + + // Ensure ctor(A) was selected over ctor(B) since A has [ActivatorUtilitiesConstructor]. + Assert.NotNull(service.A); + } + + [Fact] + public void CreateInstanceIgnoresActivatorUtilitiesConstructorAttribute() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var service = ActivatorUtilities.CreateInstance(serviceProvider); + + // Ensure ctor(A) was selected since A has [ActivatorUtilitiesConstructor]. + Assert.NotNull(service.A); + Assert.Null(service.B); + } + + [Fact] + public void CreateInstanceIgnoresActivatorUtilitiesConstructorAttribute_ReversedOrder() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + var service = ActivatorUtilities.CreateInstance(serviceProvider); + + // Ensure ctor(A) was selected since it has [ActivatorUtilitiesConstructor]. + Assert.NotNull(service.A); + Assert.Null(service.B); + } + [Fact] public void CreateInstance_ClassWithABC_MultipleCtorsWithSameLength_ThrowsAmbiguous() { @@ -662,6 +752,108 @@ public ClassWithABC_LastConstructorWithAttribute(B b, C c) : this(null, b, c) { public ClassWithABC_LastConstructorWithAttribute(A a, B b, C c) : base(a, b, c) { } } + internal class ClassWithA_And_B + { + public ClassWithA_And_B(A a) + { + A = a; + } + + public ClassWithA_And_B(B b) + { + B = b; + } + + public A A { get; } + public B B { get; } + } + + internal class ClassWithB_And_A + { + public ClassWithB_And_A(A a) + { + A = a; + } + + public ClassWithB_And_A(B b) + { + B = b; + } + + public A A { get; } + public B B { get; } + } + + internal class ClassWithA_And_B_ActivatorUtilitiesConstructorAttribute + { + [ActivatorUtilitiesConstructor] + public ClassWithA_And_B_ActivatorUtilitiesConstructorAttribute(A a) + { + A = a; + } + + public ClassWithA_And_B_ActivatorUtilitiesConstructorAttribute(B b) + { + B = b; + } + + public A A { get; } + public B B { get; } + } + + internal class ClassWithB_And_A_ActivatorUtilitiesConstructorAttribute + { + public ClassWithB_And_A_ActivatorUtilitiesConstructorAttribute(B b) + { + B = b; + } + + [ActivatorUtilitiesConstructor] + public ClassWithB_And_A_ActivatorUtilitiesConstructorAttribute(A a) + { + A = a; + } + + public A A { get; } + public B B { get; } + } + + internal class ClassWithA_And_AB_ActivatorUtilitiesConstructorAttribute + { + [ActivatorUtilitiesConstructor] + public ClassWithA_And_AB_ActivatorUtilitiesConstructorAttribute(A a) + { + A = a; + } + + public ClassWithA_And_AB_ActivatorUtilitiesConstructorAttribute(A a, B b) + { + A = a; + B = b; + } + + public A A { get; } + public B B { get; } + } + + internal class ClassWithAB_And_A_ActivatorUtilitiesConstructorAttribute + { + public ClassWithAB_And_A_ActivatorUtilitiesConstructorAttribute(A a, B b) + { + A = a; + B = b; + } + + [ActivatorUtilitiesConstructor] + public ClassWithAB_And_A_ActivatorUtilitiesConstructorAttribute(A a) + { + A = a; + } + + public A A { get; } + public B B { get; } + } + internal class FakeServiceProvider : IServiceProvider { private IServiceProvider _inner;