ConstructorInfo – How To Make Reflection in DotNet Faster for Instantiation

ConstructorInfo – How To Make Reflection in DotNet Faster for Instantiation

The post ConstructorInfo – How To Make Reflection in DotNet Faster for Instantiation appeared first on Dev Leader.

Recently I wrote an article where I wanted to compare a popular way of creating object instances with DotNet reflection to another. In that article, I put Activator.CreateInstance head-to-head with Type.InvokeMember to see which had better performance. The result was Activator.CreateInstance in one specific case — but another champion would emerge: ConstructorInfo.

In this article, I’ll explain how you can get a ConstructorInfo reference using reflection in DotNet. I’ll also expand upon the benchmarks from the previous article, showing you the code and how the results turned out.


Understanding ConstructorInfo From DotNet Reflection

In DotNet reflection, we get a powerful set of tools for inspecting assemblies, types, and members at runtime. One of the key components of this reflective capability is the ConstructorInfo class, which belongs to the System.Reflection namespace. This is where all of the goodies are — Even the ones shown in this video that can be misused in the wrong hands:

ConstructorInfo allows developers to obtain information about the constructors of a class, including their accessibility (public, private, etc.), parameters, and metadata. But one of the best parts, which we’ll be looking at in more detail, is that it enables the instantiation of objects dynamically at runtime without knowing their types at compile time.

And the best part? We’re going to see that when we compare it to these other DotNet reflection mechanisms for making new instances, it’s way faster.


Finding Constructors with Reflection in DotNet – Getting ConstructorInfo

In this section, we’ll look at how we can first get ConstructorInfo instances so that we can leverage them later for object instantiation:

  1. Get the ConstructorInfo instances from types

  2. Find the right constructor

  3. Profit!

Something like that, right? Let’s check these code examples out!

Get ConstructorInfo for All Public Constructors

To retrieve all public constructors of a class, you can use the GetConstructors method without any parameters by using a Type instance of a particular type. This method returns an array of ConstructorInfo objects representing each public constructor defined for the class.

Let’s see it in action in this code example:

using System;
using System.Reflection;

public class SampleClass
{
    public SampleClass() { }
    public SampleClass(int i) { }
    protected SampleClass(string s) { }
    private SampleClass(int i, string s) { }
}

Type typeInfo = typeof(SampleClass);
ConstructorInfo[] publicConstructors = typeInfo.GetConstructors();

foreach (var constructor in publicConstructors)
{
    Console.WriteLine(constructor.ToString());
}

When we’re thinking about dynamically invoking these things, it’s likely going to be the case that we don’t have the reference to the type though. But if we have the name of the type we’re interested in, we can use the following:

Type typeOfInterest = Type.GetType("The.Namespace.Of.Your.Type.TheTypeName");

Get ConstructorInfo Including Private and Protected Constructors

To get information about all constructors, regardless of their accessibility level, you can use the GetConstructors method with the BindingFlags parameter. This approach allows you to include non-public constructors in the results:

ConstructorInfo[] allConstructors = typeInfo.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

foreach (var constructor in allConstructors)
{
    Console.WriteLine(constructor.ToString());
}

Keep in mind that if you watched the video I linked above, this starts to get into the territory of “Should I be doing this?”. Please strongly consider if you need to be accessing non-public things — Someone likely chose that access modifier for a reason.

Get ConstructorInfo Matching a Specific Signature

If you’re looking for constructors that match a specific parameter signature, you can use GetConstructorInfo (notice that it’s singular). This takes in binding flags like we saw before as well as an array of types that you want to match.

Here’s how you could find constructors that take a single int parameter:

// Specify the parameter types of the 
// constructor you are looking for
Type[] paramTypes = new Type[] { typeof(int) };

// Use GetConstructor with the appropriate
// BindingFlags and parameter types
var constructor = typeInfo.GetConstructor(
    BindingFlags.Public | BindingFlags.Instance,
    paramTypes);

if (constructor != null)
{
    Console.WriteLine(constructor);
}
else
{
    Console.WriteLine("No matching constructor found!");
}

Note that this method will return null when there’s no match, so ensure you got what you were looking for!


Creating Object Instances Using ConstructorInfo

Once we have a ConstructorInfo instance, we can start making object instances. This is what we’ll be benchmarking in the upcoming sections!

In these examples, assume that we already have a ConstructorInfo instance called constructorInfo. We’d be getting this instance in any of the ways documented earlier in the article:

object instance = constructorInfo.Invoke(null);

The code above shows instantiating an object with a parameterless constructor. We pass in null for the list of arguments that would need to be provided — because there are none. Take note that the type we get back is an object. If we have access to the type at compile, we could cast this instance to that type… But if we have access to the instance at compile time there are probably very few good reasons why you would be doing this in the first place. If you don’t believe me, wait until you see the benchmark results.

If we want to instantiate using a constructor that takes parameters it would look like the following:

object instance = constructorInfo.Invoke(new object[] { 42 });

This code example shows a constructor with a single integer parameter defined being invoked with 42 as the single integer argument.


ConstructorInfo Performance Benchmarks

The moment you’ve all been waiting for! You might enjoy watching the video demonstrating these DotNet reflection benchmarks here:

BenchmarkDotNet Setup for Reflection Performance

Much like the previous article, I’ve just added a couple of additional scenarios for the ConstructorInfo scenarios. I wanted to mention that I added TWO scenarios for each class, and that’s because I wanted to demonstrate the performance if you had to go instantiate AND find the ConstructorInfo back-to-back. I felt like this variation compared to already having the ConstructorInfo would be interesting to take note of.

Here is the full code, which you can also find on GitHub:

//
// This code was written for the following Dev Leader content:
// https://www.devleader.ca/2024/03/14/activator-createinstance-vs-type-invokemember-a-clear-winner/
// https://www.devleader.ca/2024/03/17/constructorinfo-how-to-make-reflection-in-dotnet-faster-for-instantiation/
// https://youtu.be/Djq7eMI_L-4 
//
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

using System.Reflection;

BenchmarkRunner.Run(Assembly.GetExecutingAssembly());
//BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()).RunAllJoined();

public class ParameterlessClass
{
}

public class ClassicStringParameterClass
{
    private readonly string _value;

    public ClassicStringParameterClass(string value)
    {
        _value = value;
    }
}

public class PrimaryConstructorStringParameterClass(
    string _value)
{
}

[ShortRunJob]
public class ParameterlessClassBenchmarks
{
    private Type? _type;
    private ConstructorInfo? _constructorInfo;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _type = typeof(ParameterlessClass);
        _constructorInfo = _type.GetConstructor(Type.EmptyTypes);
    }

    [Benchmark]
    public void Constructor()
    {
        var instance = new ParameterlessClass();
    }

    [Benchmark(Baseline = true)]
    public void Activator_Create_Instance()
    {
        var instance = Activator.CreateInstance(_type!);
    }

    [Benchmark]
    public void Type_Invoke_Member()
    {
        var instance = _type!.InvokeMember(
            null,
            BindingFlags.CreateInstance,
            null,
            null,
            null);
    }

    [Benchmark]
    public void Constructor_Info_Invoke()
    {
        var instance = _constructorInfo!.Invoke(null);
    }

    [Benchmark]
    public void Find_Constructor_Info_Then_Invoke()
    {
        var constructorInfo = _type.GetConstructor(Type.EmptyTypes);
        var instance = constructorInfo!.Invoke(null);
    }
}

[ShortRunJob]
public class ClassicStringParameterClassBenchmarks
{
    private Type? _type;
    private ConstructorInfo? _constructorInfo;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _type = typeof(ClassicStringParameterClass);
        _constructorInfo = _type.GetConstructor([typeof(string)]);
    }

    [Benchmark]
    public void Constructor()
    {
        var instance = new ClassicStringParameterClass("Hello World!");
    }

    [Benchmark(Baseline = true)]
    public void Activator_Create_Instance()
    {
        var instance = Activator.CreateInstance(
            _type!,
            new[]
            {
                "Hello World!",
            });
    }

    [Benchmark]
    public void Type_Invoke_Member()
    {
        var instance = _type!
            .InvokeMember(
                null,
                BindingFlags.CreateInstance,
                null,
                null,
                new[]
                {
                    "Hello World!",
                });
    }

    [Benchmark]
    public void Constructor_Info_Invoke()
    {
        var instance = _constructorInfo!.Invoke(new[]
        {
            "Hello World!",
        });
    }

    [Benchmark]
    public void Find_Constructor_Info_Then_Invoke()
    {
        var constructorInfo = _type.GetConstructor([typeof(string)]);
        var instance = constructorInfo!.Invoke(new[]
        {
            "Hello World!",
        });
    }
}

[ShortRunJob]
public class PrimaryConstructorStringParameterClassBenchmarks
{
    private Type? _type;
    private ConstructorInfo? _constructorInfo;

    [GlobalSetup]
    public void GlobalSetup()
    {
        _type = typeof(PrimaryConstructorStringParameterClass);
        _constructorInfo = _type.GetConstructor([typeof(string)]);
    }

    [Benchmark]
    public void Constructor()
    {
        var instance = new PrimaryConstructorStringParameterClass("Hello World!");
    }

    [Benchmark(Baseline = true)]
    public void Activator_Create_Instance()
    {
        var instance = Activator.CreateInstance(
            _type!,
            new[]
            {
                "Hello World!",
            });
    }

    [Benchmark]
    public void Type_Invoke_Member()
    {
        var instance = _type!
            .InvokeMember(
                null,
                BindingFlags.CreateInstance,
                null,
                null,
                new[]
                {
                    "Hello World!",
                });
    }

    [Benchmark]
    public void Constructor_Info_Invoke()
    {
        var instance = _constructorInfo!.Invoke(new[]
        {
            "Hello World!",
        });
    }

    [Benchmark]
    public void Find_Constructor_Info_Then_Invoke()
    {
        var constructorInfo = _type.GetConstructor([typeof(string)]);
        var instance = constructorInfo!.Invoke(new[]
        {
            "Hello World!",
        });
    }
}

ConstructorInfo Benchmark Results from BenchmarkDotNet

The first set of results will be for the parameterless constructor:

BenchmarkDotNet Results for DotNet Reflection - Comparing ConstructorInfo for Parameterless Constructors

In the results above, we clearly already knew that without using reflection, we get the best speed. No brainer here. BenchmarkDotNet says it’s so fast it can’t even measure it properly. But we’ll notice that Activator.CreateInstance is technically a smidge faster here than using ConstructorInfo, even if we already had the instance ahead of time. The results are very close, and I have seen this swing the other way. So overall, these two are very comparable in this situation.

What happens if we need to use parameters though?

BenchmarkDotNet Results for DotNet Reflection - Comparing ConstructorInfo for a Constructor with One Parameter

The BenchmarkDotNet results above show that a classic style constructor taking in a single string parameter, ConstructorInfo is an order of magnitude faster than the other DotNet reflection options. Even if we need to look up the instance first, it’s still almost twice as fast as the other options!

And of course, I wanted to see if primary constructors were any different in behavior:

BenchmarkDotNet Results for DotNet Reflection - Comparing ConstructorInfo for a Primary Constructor with One Parameter

Based on the results above though, they’re right on par!


Wrapping Up ConstructorInfo and Reflection in DotNet

As we can see from the BenchmarkDotNet results, leveraging ConstructorInfo can be very performant! Only in the case where we’re dealing with a public parameterless constructor did it seem to be right on par with Activator.CreateInstance. Technically in this run it showed that it was a touch slower, but I’ve run these before and seen the opposite case too. Overall, you’ll want to consider if it makes sense for you to leverage this approach for creating object instances — but certainly don’t opt for reflection if you can easily just call new()!

If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Remember to head over to the Discord community to chat with me and other like-minded software engineers!


Want More Dev Leader Content?

  • Follow along on this platform if you haven’t already!

  • Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos:
    SUBSCRIBE FOR FREE

  • Looking for courses? Check out my offerings:
    VIEW COURSES

  • E-Books & other resources:
    VIEW RESOURCES

  • Watch hundreds of full-length videos on my YouTube channel:
    VISIT CHANNEL

  • Visit my website for hundreds of articles on various software engineering topics (including code snippets):
    VISIT WEBSITE

  • Check out the repository with many code examples from my articles and videos on GitHub:
    VIEW REPOSITORY

Did you find this article valuable?

Support Dev Leader by becoming a sponsor. Any amount is appreciated!