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:
Get the
ConstructorInfo
instances from typesFind the right constructor
…
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:
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?
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:
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
Top comments (0)