Skip to content

[Breaking change]: ActivatorUtilities.CreateInstance is creating non-empty generic collections #39216

Open
@miguelnunes94

Description

@miguelnunes94

Description

Generic collections that have copy constructors are initialized with elements, when using ActivatorUtilities.CreateInstance/ActivatorUtilities.GetServiceOrCreateInstance.

Example:

    public interface ITest { }

    public class Test : ITest { }

    public class Test2 : ITest { }

    public static void Main(string[] args)
    {

        var serviceCollection = new ServiceCollection();

        serviceCollection.AddTransient<ITest, Test>();
        serviceCollection.AddTransient<ITest, Test2>();

        var services = serviceCollection.BuildServiceProvider();

        var collection = ActivatorUtilities.CreateInstance<ObservableCollection<ITest>>(services);
    }

Version

.NET 8 GA

Previous behavior

The collection object is an empty instance of ObservableCollection.
ActivatorUtilities.CreateInstance used the constructor public ObservableCollection();

New behavior

The collection object is an instance of ObservableCollection with two elements (an instance of Test and another of Test2).
ActivatorUtilities.CreateInstance is now choosing the constructor public ObservableCollection(IEnumerable<T> collection);

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load or execute, and if so, require recompilation.
  • Source incompatible: When recompiled using the new SDK or component or to target the new runtime, existing source code may require source changes to compile successfully.
  • Behavioral change: Existing binaries may behave differently at run time.

Reason for change

It should be related with:

Currently, ActivatorUtilities.CreateInstance is choosing the constructor with most parameters that can be resolved from IServiceProviderIsService (since I'm not given any parameter to be used on the constructor), while previously it would pick the constructor with most parameters that could be resolved from the given ones (choosing the parameteless constructor, in this example).

Recommended action

I'm not sure on the workaround.

This issue is affecting a custom JSON deserializer that uses ActivatorUtilities.GetServiceOrCreateInstance to instantiate the needed types (some of them are .Net generic collections). I can't change the types being sent on the JSON and I know that collections must be empty after initalization (and then filled up with the elements from the JSON payload), I maybe register custom collections on the dependency injection container to replace the .Net ones (inheritance + construtor overload). Other workarounds are very welcome.

Maybe it would make sense to make sure that ActivatorUtilities.GetServiceOrCreateInstance chooses the parameterless constructor on those cases - generic collections with copy constructors (by moving the parameteless construtor afterwards the on requesting IEnumerable<T> and by adding the ActivatorUtilitiesConstructorAttribute to it).

Feature area

C#, Core .NET libraries, Extensions

Affected APIs

ActivatorUtilities.CreateInstance
ActivatorUtilities.GetServiceOrCreateInstance


Associated WorkItem - 206386

Metadata

Metadata

Assignees

Labels

🏁 Release: .NET 8Work items for the .NET 8 releasebreaking-changeIndicates a .NET Core breaking change

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions