Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 of 3 tasks
miguelnunes94 opened this issue Jan 22, 2024 · 2 comments
Assignees
Labels
breaking-change Indicates a .NET Core breaking change 🏁 Release: .NET 8 Work items for the .NET 8 release doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3

Comments

@miguelnunes94
Copy link

miguelnunes94 commented Jan 22, 2024

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

@miguelnunes94 miguelnunes94 added breaking-change Indicates a .NET Core breaking change doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3 labels Jan 22, 2024
@dotnet-bot dotnet-bot added the ⌚ Not Triaged Not triaged label Jan 22, 2024
@gewarren gewarren added 🏁 Release: .NET 8 Work items for the .NET 8 release and removed ⌚ Not Triaged Not triaged labels Jan 22, 2024
@adegeo
Copy link
Contributor

adegeo commented Jan 25, 2024

Ignore this comment. I'm testing something

@gewarren gewarren added the 🗺️ reQUEST Triggers an issue to be imported into Quest. label Feb 2, 2024
@github-actions github-actions bot added 📌 seQUESTered Identifies that an issue has been imported into Quest. and removed 🗺️ reQUEST Triggers an issue to be imported into Quest. labels Feb 2, 2024
@gewarren
Copy link
Contributor

@steveharter Should this be documented as a breaking change?

@BillWagner BillWagner added 🗺️ reQUEST Triggers an issue to be imported into Quest. and removed 📌 seQUESTered Identifies that an issue has been imported into Quest. labels Mar 21, 2024
@gewarren gewarren removed the 🗺️ reQUEST Triggers an issue to be imported into Quest. label May 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Indicates a .NET Core breaking change 🏁 Release: .NET 8 Work items for the .NET 8 release doc-idea Indicates issues that are suggestions for new topics [org][type][category] Pri1 High priority, do before Pri2 and Pri3
Projects
None yet
Development

No branches or pull requests

5 participants