Skip to content

[API Proposal]: Generic Activator API to create a type from a possibly non-public parameterless constructor #85541

Closed as not planned
@jkoritzinsky

Description

@jkoritzinsky

Background and motivation

To construct a object of type T from a parameterless constructor, we have a few API options today. If we know that the constructor is public, we can use Activator.CreateInstance<T>() and Activator.CreateInstance(Type). If the constructor may be non-public, we only have a non-generic Activator.CreateInstance(Type, bool) method.

For our SafeHandleMarshaller type that we use in the interop source generators, we need to support non-public constructors for backward-compatibility reasons. As a result, we need to use Activator.CreateInstance(Type, bool) to optionally support a non-public default constructor.

CoreCLR accelerates both Activator.CreateInstance<T>() and Activator.CreateInstance(Type, bool) to be almost as fast as writing new T() with a specific T, so adding a new generic Activator.CreateInstance method to support constructing an object from a non-public constructor isn't necessary.

NativeAOT has a very powerful optimization path for Activator.CreateInstance<T>() to avoid pulling in the reflection stack. It does not have this optimization for Activator.CreateInstance(Type, bool), and it cannot have this optimization in a non-generic context. As a result, the new SafeHandleMarshaller type roots the reflection stack, increasing the size of any apps that use it by quite a bit. Since LibraryImports with SafeHandles are used in the code for basically every application type (Web, Console, UI), we end up always rooting the reflection stack. If we add this API, we can implement an intrinsic implementation on NativeAOT that does not root the reflection stack and avoids the size regression.

cc: @dotnet/ilc-contrib

API Proposal

namespace System;

public static class Activator
{
+    public void CreateInstance<T>(bool nonPublic);
}

API Usage

T instance = Activator.CreateInstance<T>(nonPublic: true);

Alternative Designs

We can just have this method as internal in System.Private.CoreLib to avoid expanding the public API surface of Activator, as the SafeHandleMarshaller type will live in CoreLib.

We could also introduce a custom intrinsic in NativeAOT's CoreLib that the SafeHandleMarshaller could use on NativeAOT.

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions