Reflection in dotnet is incredibly powerful -- but with great power comes great responsibilities. Aside from all sorts of misuse that can arise with reflection, one of the reasons that dotnet reflection gets flack is because of performance. For that reason, I put this article together to talk about a comparison between two popular ways that you can create object instances. This will be the battle of Activator.CreateInstance vs Type.InvokeMember.
In this article, I'll explain how you can use each to create new instances of types and I'll also provide some benchmark details to explain who comes out on top. Of course, there's going to be a little surprise. So, let's dive into reflection in dotnet and compare Activator.CreateInstance vs Type.InvokeMember methods!
What's In This Article: Activator.CreateInstance vs Type.InvokeMember
Remember to check out these platforms:
// FIXME: social media icons coming back soon!
Understanding Reflection in DotNet
DotNet Reflection is a powerful feature that allows you to inspect and manipulate your types at runtime. It provides the ability to dynamically load assemblies, examine and modify their metadata, and create instances of types that are not known at compile-time. I use this all of the time in my C# applications because I leverage plugin architectures all of the time.
Reflection plays an important role in many advanced programming scenarios. It enables us to build flexible and extensible software solutions by providing the ability to perform operations on types, methods, properties, and fields that are not known at design-time. It's also these powerful abilities that allow folks to use it for questionable reasons in their regular development -- they hit a wall in their design but reflection allows them to walk right around it.
However, I feel that as a C# developer, it's important to understand Reflection in DotNet because it opens up a whole new world of possibilities. With Reflection, you can build generic frameworks, implement dependency injection, create dynamic proxies, and much more. But these generally aren't the core of what you're building -- they're supporting pieces. That's why they might be a good fit for it compared to the bulk of your business logic using reflection.
One of the key benefits of Reflection is its ability to dynamically create instances of types using either Activator.CreateInstance
or Type.InvokeMember
. These methods allow us to create objects without having knowledge of the concrete type at compile-time. In the following sections, we'll explore these two methods of creating instances using Reflection as we compare Activator.CreateInstance
vs Type.InvokeMember
.
Creating Instances Using Activator.CreateInstance
In C#, the Activator.CreateInstance
method is one of the most popular ways that developers use to create instances via reflection. It allows the creation of new instances of classes at runtime, even without having knowledge of their specific types beforehand. This method belongs to the System.Activator
class and is commonly used in scenarios where dynamic instantiation is required.
The primary purpose of Activator.CreateInstance
is to dynamically create instances of classes -- so if you have access to the type at compile time there's probably not a good reason for you to be using this! Activator.CreateInstance
can be particularly useful in situations where the type of object to create is not known until runtime, such as when loading plugins or working with dynamically loaded assemblies. It eliminates the need for hardcoding explicit constructors and provides flexibility in object creation.
Advantages and Disadvantages of using Activator.CreateInstance
Using Activator.CreateInstance
offers several advantages over normal object instantiation:
- Allows for late-bound object creation, which can be beneficial in scenarios where object types can vary at runtime.
- Can simplify the codebase by eliminating the need for switch statements or if-else conditions to handle different object types.
- Provides a dynamic and extensible approach to object creation.
However, there are also some disadvantages to consider when using Activator.CreateInstance
:
- The performance overhead of using reflection can be higher compared to direct instantiation due to the additional steps involved in resolving types at runtime.
Activator.CreateInstance
generally relies on the existence of a public parameterless constructor. Otherwise, you need to consistently know which arguments to pass in -- challenging if you're dynamically doing this for many different types.- Prone to bugs when the target type is modified because there are no compile-time checks for signature compatibility.
Code Examples of Using Activator.CreateInstance
The following code examples demonstrate how to use Activator.CreateInstance
to create instances dynamically:
// Example 1: Creating an instance of a known type
Type objectType = typeof(MyClass);
object instance = Activator.CreateInstance(objectType);
In example 1, we use typeof
to obtain the Type
object representing the known class MyClass
. Then, we use Activator.CreateInstance
to create a new instance of MyClass
.
// Example 2: Creating an instance of an unknown type at runtime
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
object dynamicInstance = Activator.CreateInstance(unknownType);
In example 2, we have an unknown type represented by a string typeName
. We use Type.GetType
to obtain the Type
object based on the provided type name. Finally, Activator.CreateInstance
is used to create a new instance of the dynamically determined type.
We'll look at one more example where we can pass parameters in for the constructor -- again, making the assumption that we'll know the signature since we can't prove it at compile time via this method:
// Example 3: Creating an instance with constructor parameters:
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
Object dynamicInstance = Activator.CreateInstance(
unknownType,
new[]
{
"Hello World!", // this is the single string parameter!
});
Creating Instances Using Type.InvokeMember
Type.InvokeMember
is a method available to us from Reflection in DotNet that allows us to dynamically create instances of a type. It provides a flexible way to instantiate objects at runtime by utilizing the information about the type at hand. For these reasons, it's very similar in terms of how you might go leverage it to create instances of objects.
Advantages and Disadvantages of using Type.InvokeMember
Here are some general advantages to using Type.InvokeMember
over normal object instantiation:
- Allows for late-bound object creation, which can be beneficial in scenarios where object types can vary at runtime.
- Can simplify the codebase by eliminating the need for switch statements or if-else conditions to handle different object types.
- Provides a dynamic and extensible approach to object creation.
Wait a second... Isn't this the same list that we saw above for Activator.CreateInstance
? That's right. So let's cut this part short. We're not going to see any big difference until we start looking at performance -- and perhaps in some very specific edge cases. But overall both provide very comprehensive ways to dynamically instantiate objects and InvokeMember is a bit more verbose since it handles more than just constructors.
Let's check out some code before hitting the benchmarks.
Code Example of Using Type.InvokeMember
Here is an example code snippet demonstrating the usage of Type.InvokeMember
to create an instance of a type dynamically:
// Example 1: Creating an instance of a known type
Type objectType = typeof(MyClass);
var instance = objectType
.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
null);
In the above example, we first obtain the Type object representing the class "MyClass". We then use Type.InvokeMember to create an instance of that class and assign it to the "instance" variable. This allows us to create an object of "MyClass" dynamically without explicitly specifying the class name.
And to do this without knowing the class at compile-time, it's very much like before. This part has nothing to do with InvokeMember though:
// Example 2: Creating an instance of an unknown type at runtime
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
var instance = objectType.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
null);
And finally, if we need to pass in some constructor parameters then we can do that as well:
// Example 3: Creating an instance with constructor parameters:
string typeName = "MyNamespace.MyClass";
Type unknownType = Type.GetType(typeName);
var instance = objectType.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
new[]
{
"Hello World!",
});
Benchmarking Activator.CreateInstance vs Type.InvokeMember
Alright -- onto the juicy stuff. We're going to look at benchmarking these different approaches to see if aside from API usage we come up with anything different between the two. If you don't have much experience using BenchmarkDotNet and want to see more, you can check out this video on how to use BenchmarkDotNet:
BenchmarkDotNet Setup for Reflection Performance
I figured I'd come up with three situations we could run benchmarks against using BenchmarkDotNet:
- Parameterless constructor class
- Constructor with a single string parameter
- Primary constructor with a single string parameter
I wanted to toss in primary constructors because I know that's a feature getting a lot of people up in arms -- might as well get some data on it! Here are the classes we'll be instantiating, for reference:
public class ParameterlessClass
{
}
public class ClassicStringParameterClass
{
private readonly string _value;
public ClassicStringParameterClass(string value)
{
_value = value;
}
}
public class PrimaryConstructorStringParameterClass(
string _value)
{
}
As for benchmarks, let's check out the following classes that we'll be running. Keep in mind that I am using Activator.CreateInstance
as the baseline because I want to compare Activator.CreateInstance
vs Type.InvokeMember
-- I am only including the normal constructor pathway just as a reference. You can find all of this code on GitHub as well:
[ShortRunJob]
public class ParameterlessClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(ParameterlessClass);
}
[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);
}
}
[ShortRunJob]
public class ClassicStringParameterClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(ClassicStringParameterClass);
}
[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!",
});
}
}
[ShortRunJob]
public class PrimaryConstructorStringParameterClassBenchmarks
{
private Type? _type;
[GlobalSetup]
public void GlobalSetup()
{
_type = typeof(PrimaryConstructorStringParameterClass);
}
[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!",
});
}
}
Activator.CreateInstance vs Type.InvokeMember: Who is the Champion?!
When we pit these two Reflection methods head to head, the winner is... situational. In one of the most common cases, I'd say there's a very clear winner but for the others, they're very close. But make sure you read the conclusion because this isn't the end of the story.
The first benchmark we're going to look at is for a parameterless constructor:
Clear winner here: Activator.CreateInstance
, almost by an order of magnitude. If you don't have any parameters on your constructor, your best bet is this one.
Next, let's check out Activator.CreateInstance
vs Type.InvokeMember
for a constructor taking in a single string parameter:
The winner? Not so obvious. These are basically neck-and-neck here, and even though Activator.CreateInstance
comes out a smidge ahead, it's nearly negligible.
The last scenario to look at is going to be for primary constructors, and in this case, a primary constructor that takes in a single string parameter:
The winner: Type.InvokeMember
, but only by a little bit. Very interesting that this is the reverse of what we saw in the previous benchmark!
Wrapping Up Activator.CreateInstance vs Type.InvokeMember
When it comes to the performance results of Activator.CreateInstance
vs Type.InvokeMember
, there's a clear winner for the parameterless constructor case: Activator.CreateInstance
. But when we get into requiring parameters or using primary constructors with parameters, it starts to even out or even favor Type.InvokeMember
.
But this is only the FIRST part of the picture... There's one more scenario we'll look at that is the *true* winner, which you can read all about in this article here. It continues with these examples and provides updated benchmarks for you as well.
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!
Affiliations
These are products & services that I trust, use, and love. I get a kickback if you decide to use my links. There’s no pressure, but I only promote things that I like to use!
- BrandGhost: My social media content and scheduling tool that I use for ALL of my content!
- RackNerd: Cheap VPS hosting options that I love for low-resource usage!
- Contabo: Affordable VPS hosting options!
- ConvertKit: The platform I use for my newsletter!
- SparkLoop: Helps add value to my newsletter!
- Opus Clip: Tool for creating short-form videos!
- Newegg: For all sorts of computer components!
- Bulk Supplements: Huge selection of health supplements!
- Quora: I answer questions when folks request them!