Description
Description
Selecting the preferred constructor by ActivatorUtilitiesConstructor
attribute works differently depending on the order of constructors.
Reproduction Steps
Let's consider the following class:
public class Foo
{
public Foo(Bar bar)
{
this.Bar = bar;
}
public Foo(Baz bar)
{
this.Baz = bar;
}
public Bar Bar { get; }
public Baz Baz { get; }
}
public class Bar {}
public class Baz {}
Let's assume that I want to instantiate this class using the Dependency Injection container:
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Foo>();
serviceCollection.AddTransient<Bar>();
serviceCollection.AddTransient<Baz>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var foo = serviceProvider.GetService<Foo>();
This obviously fails because ActivatorUtilities
class finds two matching constructors.
Fortunately there is a [ActivatorUtilitiesConstructorAttribute](https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilitiesConstructorAttribute.cs)
so let's adjust the class Foo
:
public class Foo
{
[ActivatorUtilitiesConstructor]
public Foo(Bar bar)
{
this.Bar = bar;
}
public Foo(Baz bar)
{
this.Baz = bar;
}
public Bar Bar { get; }
public Baz Baz { get; }
}
// the same wiring as before
var foo = serviceProvider.GetService<Foo>();
This still throws the same error.
Expected behavior
If [ActivatorUtilitiesConstructor]
is used the constructor should be prioritized above all other constructors if all its dependencies are satisfied, regardless of the order of the constructors in the source code.
Actual behavior
I have digged in the source code and I think the reason is a mistake in ActivatorUtilities
class.
The following snippet of code (https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs#L95)
if (isPreferred)
{
if (seenPreferred)
{
ThrowMultipleCtorsMarkedWithAttributeException();
}
if (length == -1)
{
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
}
}
if (isPreferred || bestLength < length)
{
bestLength = length;
bestMatcher = matcher;
multipleBestLengthFound = false;
}
else if (bestLength == length)
{
multipleBestLengthFound = true;
}
isPreferred
is properly set to true based on the existance of the attribute on the first constructor. But when the second constructor (public Foo(Baz baz)
) is analyzed the condition in line 108 (if (isPreferred || bestLength < length)
) is false
, but the else statement in line 114 (else if (bestLength == length)
) is evaluated to true
and therefore the constructor is also considered to be the best candidate.
If the constructors appeared in an opposite order the condition in line 108
would be true and the code would have worked properly.
Regression?
No response
Known Workarounds
Add the constructor decorated with ActivatorUtilitiesConstructorAttribute
after all other constructors in your class.
Configuration
No response
Other information
No response