visit
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 saw Activator.CreateInstance emerge as winner in one specific case — but another champion emerges: ConstructorInfo.
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.
In this section, we’ll look at how we can first get ConstructorInfo
instances so that we can leverage them later for object instantiation:
ConstructorInfo
instances from types
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.
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());
}
Type typeOfInterest = Type.GetType("The.Namespace.Of.Your.Type.TheTypeName");
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());
}
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!");
}
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.
object instance = constructorInfo.Invoke(new object[] { 42 });
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:
//
// This code was written for the following Dev Leader content:
// //www.devleader.ca/2024/03/14/activator-createinstance-vs-type-invokemember-a-clear-winner/
// //www.devleader.ca/2024/03/17/constructorinfo-how-to-make-reflection-in-dotnet-faster-for-instantiation/
// //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!",
});
}
}
The first set of results will be for the parameterless constructor:
In the results above, we 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.
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:
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()
!