API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

Description

@GrabYourPitchforks

(See also #32520.)

API proposal

namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

Discussion

Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

See below for some concrete samples.

System.Text.Json

if(realMethod==null)
{
LocalBuilderlocal=generator.DeclareLocal(type);
generator.Emit(OpCodes.Ldloca_S,local);
generator.Emit(OpCodes.Initobj,type);
generator.Emit(OpCodes.Ldloc,local);
generator.Emit(OpCodes.Box,type);
}
else
{
generator.Emit(OpCodes.Newobj,realMethod);
}

ASP.NET Core

dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

Other runtime + libraries

// Implemet the static factory
// public object Create(IDictionary<string, object>)
// {
// return new <ProxyClass>(dictionary);
// }
MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
factoryIL.Emit(OpCodes.Ldarg_0);
factoryIL.Emit(OpCodes.Newobj,proxyCtor);
factoryIL.Emit(OpCodes.Ret);

Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

Shortcomings, not solved here

This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

I hope to have a better solution for this specific scenario in a future issue.

Stretch goal APIs

The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

Consider:

publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

Metadata

Metadata

Assignees

Labels

Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

Type

No type

Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

    Description

    @GrabYourPitchforks

    (See also #32520.)

    API proposal

    namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

    Discussion

    Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

    APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

    See below for some concrete samples.

    System.Text.Json

    if(realMethod==null)
    {
    LocalBuilderlocal=generator.DeclareLocal(type);
    generator.Emit(OpCodes.Ldloca_S,local);
    generator.Emit(OpCodes.Initobj,type);
    generator.Emit(OpCodes.Ldloc,local);
    generator.Emit(OpCodes.Box,type);
    }
    else
    {
    generator.Emit(OpCodes.Newobj,realMethod);
    }

    ASP.NET Core

    dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

    Other runtime + libraries

    // Implemet the static factory
    // public object Create(IDictionary<string, object>)
    // {
    // return new <ProxyClass>(dictionary);
    // }
    MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
    ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
    factoryIL.Emit(OpCodes.Ldarg_0);
    factoryIL.Emit(OpCodes.Newobj,proxyCtor);
    factoryIL.Emit(OpCodes.Ret);

    Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

    These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

    Shortcomings, not solved here

    This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

    It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

    I hope to have a better solution for this specific scenario in a future issue.

    Stretch goal APIs

    The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

    Consider:

    publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

    In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

    I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

    Metadata

    Metadata

    Assignees

    Labels

    Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

    Type

    No type

    Projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

      Description

      @GrabYourPitchforks

      (See also #32520.)

      API proposal

      namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

      Discussion

      Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

      APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

      See below for some concrete samples.

      System.Text.Json

      if(realMethod==null)
      {
      LocalBuilderlocal=generator.DeclareLocal(type);
      generator.Emit(OpCodes.Ldloca_S,local);
      generator.Emit(OpCodes.Initobj,type);
      generator.Emit(OpCodes.Ldloc,local);
      generator.Emit(OpCodes.Box,type);
      }
      else
      {
      generator.Emit(OpCodes.Newobj,realMethod);
      }

      ASP.NET Core

      dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

      Other runtime + libraries

      // Implemet the static factory
      // public object Create(IDictionary<string, object>)
      // {
      // return new <ProxyClass>(dictionary);
      // }
      MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
      ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
      factoryIL.Emit(OpCodes.Ldarg_0);
      factoryIL.Emit(OpCodes.Newobj,proxyCtor);
      factoryIL.Emit(OpCodes.Ret);

      Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

      These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

      Shortcomings, not solved here

      This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

      It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

      I hope to have a better solution for this specific scenario in a future issue.

      Stretch goal APIs

      The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

      Consider:

      publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

      In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

      I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

      Metadata

      Metadata

      Assignees

      Labels

      Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

      Type

      No type

      Projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

        Description

        @GrabYourPitchforks

        (See also #32520.)

        API proposal

        namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

        Discussion

        Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

        APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

        See below for some concrete samples.

        System.Text.Json

        if(realMethod==null)
        {
        LocalBuilderlocal=generator.DeclareLocal(type);
        generator.Emit(OpCodes.Ldloca_S,local);
        generator.Emit(OpCodes.Initobj,type);
        generator.Emit(OpCodes.Ldloc,local);
        generator.Emit(OpCodes.Box,type);
        }
        else
        {
        generator.Emit(OpCodes.Newobj,realMethod);
        }

        ASP.NET Core

        dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

        Other runtime + libraries

        // Implemet the static factory
        // public object Create(IDictionary<string, object>)
        // {
        // return new <ProxyClass>(dictionary);
        // }
        MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
        ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
        factoryIL.Emit(OpCodes.Ldarg_0);
        factoryIL.Emit(OpCodes.Newobj,proxyCtor);
        factoryIL.Emit(OpCodes.Ret);

        Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

        These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

        Shortcomings, not solved here

        This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

        It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

        I hope to have a better solution for this specific scenario in a future issue.

        Stretch goal APIs

        The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

        Consider:

        publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

        In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

        I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

        Metadata

        Metadata

        Assignees

        Labels

        Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

        Type

        No type

        Projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

          Description

          @GrabYourPitchforks

          (See also #32520.)

          API proposal

          namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

          Discussion

          Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

          APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

          See below for some concrete samples.

          System.Text.Json

          if(realMethod==null)
          {
          LocalBuilderlocal=generator.DeclareLocal(type);
          generator.Emit(OpCodes.Ldloca_S,local);
          generator.Emit(OpCodes.Initobj,type);
          generator.Emit(OpCodes.Ldloc,local);
          generator.Emit(OpCodes.Box,type);
          }
          else
          {
          generator.Emit(OpCodes.Newobj,realMethod);
          }

          ASP.NET Core

          dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

          Other runtime + libraries

          // Implemet the static factory
          // public object Create(IDictionary<string, object>)
          // {
          // return new <ProxyClass>(dictionary);
          // }
          MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
          ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
          factoryIL.Emit(OpCodes.Ldarg_0);
          factoryIL.Emit(OpCodes.Newobj,proxyCtor);
          factoryIL.Emit(OpCodes.Ret);

          Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

          These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

          Shortcomings, not solved here

          This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

          It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

          I hope to have a better solution for this specific scenario in a future issue.

          Stretch goal APIs

          The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

          Consider:

          publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

          In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

          I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

          Metadata

          Metadata

          Assignees

          Labels

          Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

          Type

          No type

          Projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

            Description

            @GrabYourPitchforks

            (See also #32520.)

            API proposal

            namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

            Discussion

            Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

            APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

            See below for some concrete samples.

            System.Text.Json

            if(realMethod==null)
            {
            LocalBuilderlocal=generator.DeclareLocal(type);
            generator.Emit(OpCodes.Ldloca_S,local);
            generator.Emit(OpCodes.Initobj,type);
            generator.Emit(OpCodes.Ldloc,local);
            generator.Emit(OpCodes.Box,type);
            }
            else
            {
            generator.Emit(OpCodes.Newobj,realMethod);
            }

            ASP.NET Core

            dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

            Other runtime + libraries

            // Implemet the static factory
            // public object Create(IDictionary<string, object>)
            // {
            // return new <ProxyClass>(dictionary);
            // }
            MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
            ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
            factoryIL.Emit(OpCodes.Ldarg_0);
            factoryIL.Emit(OpCodes.Newobj,proxyCtor);
            factoryIL.Emit(OpCodes.Ret);

            Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

            These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

            Shortcomings, not solved here

            This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

            It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

            I hope to have a better solution for this specific scenario in a future issue.

            Stretch goal APIs

            The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

            Consider:

            publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

            In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

            I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

            Metadata

            Metadata

            Assignees

            Labels

            Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

            Type

            No type

            Projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

              Description

              @GrabYourPitchforks

              (See also #32520.)

              API proposal

              namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

              Discussion

              Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

              APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

              See below for some concrete samples.

              System.Text.Json

              if(realMethod==null)
              {
              LocalBuilderlocal=generator.DeclareLocal(type);
              generator.Emit(OpCodes.Ldloca_S,local);
              generator.Emit(OpCodes.Initobj,type);
              generator.Emit(OpCodes.Ldloc,local);
              generator.Emit(OpCodes.Box,type);
              }
              else
              {
              generator.Emit(OpCodes.Newobj,realMethod);
              }

              ASP.NET Core

              dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

              Other runtime + libraries

              // Implemet the static factory
              // public object Create(IDictionary<string, object>)
              // {
              // return new <ProxyClass>(dictionary);
              // }
              MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
              ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
              factoryIL.Emit(OpCodes.Ldarg_0);
              factoryIL.Emit(OpCodes.Newobj,proxyCtor);
              factoryIL.Emit(OpCodes.Ret);

              Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

              These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

              Shortcomings, not solved here

              This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

              It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

              I hope to have a better solution for this specific scenario in a future issue.

              Stretch goal APIs

              The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

              Consider:

              publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

              In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

              I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

              Metadata

              Metadata

              Assignees

              Labels

              Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

              Type

              No type

              Projects

                Milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                API proposal: Activator.CreateFactory and ConstructorInfo.CreateDelegate #36194

                Description

                @GrabYourPitchforks

                (See also #32520.)

                API proposal

                namespaceSystem{publicstaticclassActivator{// EXISTING APIs (abridged):publicstaticTCreateInstance<T>();publicstaticobject?CreateInstance(Typetype);publicstaticobject?CreateInstance(Typetype,boolnonPublic);// NEW PROPOSED APIs:publicstaticFunc<T>CreateFactory<T>();// no new() constraint, matches existing APIpublicstaticFunc<object?>CreateFactory(Typetype);publicstaticFunc<object?>CreateFactory(Typetype,boolnonPublic);}}// STRETCH GOAL APIs (see end of proposal):namespaceSystem.Reflection{publicclassConstructorInfo{publicstaticTDelegateCreateDelegate<TDelegate>();publicstaticDelegateCreateDelegate(TypedelegateType);}}// REMOVED FROM PROPOSAL/*namespace System.Runtime.CompilerServices{ public static class RuntimeHelpers { // EXISTING API: public static object GetUninitializedObject(Type type); // NEW PROPOSED API: public static Func<object> GetUninitializedObjectFactory(Type type); }}*/

                Discussion

                Instantiating objects whose types are not known until runtime is fairly common practice among frameworks and libraries. Deserializers perform this task frequently. Web frameworks and DI frameworks might create per-request instances of objects, then destroy those objects at the end of the request.

                APIs like Activator.CreateInstance and the System.Reflection surface area can help with this to a large extent. However, those are sometimes seen as heavyweight solutions. We've built some caching mechanisms into the framework to suppose these use cases. But it's still fairly common for high-performance frameworks to bypass Activator and the reflection stack and to go straight to manual codegen. See below for some examples.

                See below for some concrete samples.

                System.Text.Json

                if(realMethod==null)
                {
                LocalBuilderlocal=generator.DeclareLocal(type);
                generator.Emit(OpCodes.Ldloca_S,local);
                generator.Emit(OpCodes.Initobj,type);
                generator.Emit(OpCodes.Ldloc,local);
                generator.Emit(OpCodes.Box,type);
                }
                else
                {
                generator.Emit(OpCodes.Newobj,realMethod);
                }

                ASP.NET Core

                dotnet/aspnetcore#14615 (though they're using TypeBuilder to work around this right now)

                Other runtime + libraries

                // Implemet the static factory
                // public object Create(IDictionary<string, object>)
                // {
                // return new <ProxyClass>(dictionary);
                // }
                MethodBuilderfactoryMethodBuilder=proxyTypeBuilder.DefineMethod(MetadataViewGenerator.MetadataViewFactoryName,MethodAttributes.Public|MethodAttributes.Static,typeof(object),CtorArgumentTypes);
                ILGeneratorfactoryIL=factoryMethodBuilder.GetILGenerator();
                factoryIL.Emit(OpCodes.Ldarg_0);
                factoryIL.Emit(OpCodes.Newobj,proxyCtor);
                factoryIL.Emit(OpCodes.Ret);

                Ref emit incurs a substantial upfront perf hit, but it does generally provide a win amortized over the lifetime of the cached method as it's invoked over and over again. However, this comes with its own set of problems. It's difficult for developers to get the exact IL correct across the myriad edge cases that might exist. It's not very memory-efficient. And as runtimes that don't allow codegen become more commonplace, it complicates the callers' code to have to decide the best course of action to take for any given runtime.

                These proposed APIs attempt to solve the problem of creating a basic object factory using the best mechanism applicable to the current runtime. The exact mechanism used can vary based on runtime: perhaps it's codegen, perhaps it's reflection, perhaps it's something else. But the idea is that the performance of these APIs should rival the best hand-rolled implementations that library authors can create.

                Shortcomings, not solved here

                This API is not a panacea to address all performance concerns developers have with the reflection stack. For example, this won't change the perf characteristics of MethodInfo.Invoke. But it could be used to speed up the existing Activator.CreateInstance<T> APIs and to make other targeted improvements. In general, this API provides an alternative pattern that developers can use so that they don't have to roll solutions themselves.

                It also does not fully address the concern of calls to parameterized ctors, such as you might find in DI systems. The API RuntimeHelpers.GetUninitializedObjectFactory does help with this to some extent. The caller can cache that factory to instantiate "blank" objects quickly, then use whatever existing fast mechanism they wish to call the object's real ctor over the newly allocated instance.

                I hope to have a better solution for this specific scenario in a future issue.

                Stretch goal APIs

                The APIs ConstructorInfo.CreateDelegate<TDelegate>() and friends are meant to allow invoking parameterless or parameterful ctors. These are primarily useful when the objects you're constructing have a common signature in their constructors, but you don't know the actual type of the object at compile time.

                Consider:

                publicclassMyService{publicMyService(IServiceProviderserviceProvider){}}publicclassMyOtherService{publicMyOtherService(IServiceProviderserviceProvider){}}

                In these cases, the caller would get the ConstructorInfo they care about, then call constructorInfo.CreateDelegate<Func<IServiceProvider, object>>(). Depending on which ConstructorInfo was provided, the returned delegate will create either a MyService or a MyOtherService, calling the appropriate ctor with the caller-provided IServiceProvider.

                I say "stretch goal" because the parameter shuffling involved here would involve spinning up the JIT. We can keep this overhead to a minimum because within the runtime we can take shortcuts that non-runtime components can't take with the typical DynamicMethod-based way of doing things. So while there's less JIT cost compared to a standard DynamicMethod-based solution, there's still some JIT cost, so it doesn't fully eliminate startup overhead. (This overhead isn't much different than the overhead the runtime already incurs when it sees code like Func<string, bool> func = string.IsNullOrEmpty;, since the runtime already needs to create a parameter shuffling thunk for this scenario.)

                Metadata

                Metadata

                Assignees

                Labels

                Cost:MWork that requires one engineer up to 2 weeksapi-approvedAPI was approved in API review, it can be implementedarea-System.Reflection

                Type

                No type

                Projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions