Skip to content

ActivatorUtilities.CreateInstance depends on the order of constructors' definitions #46132

Closed
@quixoticaxis

Description

@quixoticaxis

Description

Current behavior

The effect of calling ActivatorUtilities.CreateInstance methods (any of the overloads) depends on the order in which the constructors of the type being created are defined. The instance of the class is created if constructors are defined in one order, but the creation throws if constructors are defined in reversed order (see the code below).

Expected behavior

The call throws because it failed to disambiguate the appropriate constructor. This behavior will be in-line with the behavior of Activator.CreateInstance.

Reproduction

using Microsoft.Extensions.DependencyInjection;

using static System.Console;

namespace AUCI
{
    public class A { }
    public class B { }
    public class C { }
    public class S { }

    public class Creatable
    {
        public Creatable(
            A a,
            B b,
            C c,
            S s)
        {
            WriteLine("Creatable.Long");
        }
        
        public Creatable(
            A a,
            C c,
            S s)
        {
            WriteLine("Creatable.Short");
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddScoped<S>();
            using var provider = services.BuildServiceProvider();

            var instance = ActivatorUtilities.CreateInstance(
                provider,
                typeof(Creatable),
                // Please, note that the order of parameters does not match the order of constructors' arguments
                new C(), new A()); 
        }       
    }
}

The code above throws when invoking ActivatorUtilities.CreateInstance, but works fine if we define the constructors in reversed order:

public class Creatable
    {        
        public Creatable(
            A a,
            C c,
            S s)
        {
            WriteLine("Creatable.Short");
        }

        public Creatable(
            A a,
            B b,
            C c,
            S s)
        {
            WriteLine("Creatable.Long");
        }
    }

It's counter-intuitive, IMHO.

Additional findings

I've looked through the implementation and debugged it:

  1. the first call to ConstructorMatcher.Match returns 0 (meaning that the constructor is the worst possible match, the constructor does not match our parameters at all);
  2. the second call to ConstructorMatcher.Match returns 0, so it's not better than the first one and we dismiss it;
  3. we create the instance with the best match we've found (even knowing that it is ambiguous and does not match the parameters at all).

Configuration

The issue is not specific to configuration.

Regression?

Does not seem to be a regression.

Other information

It's probably a corner case, but it still would be great to remove the undefined (as I understand it) behavior.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions