paint-brush
Head to Head Performance of Activator.CreateInstance vs Type.InvokeMemberby@devleader

Head to Head Performance of Activator.CreateInstance vs Type.InvokeMember

by Dev LeaderMarch 19th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Headlines: Unveiling the Battle: Activator.CreateInstance vs Type.InvokeMember in .NET Reflection Comparing Activator.CreateInstance and Type.InvokeMember: Performance Showdown in .NET Activator.CreateInstance vs Type.InvokeMember: A Deep Dive into .NET Reflection Performance Exploring .NET Reflection Performance: Activator.CreateInstance vs Type.InvokeMember Meta Keyword Tags: .NET Reflection Activator.CreateInstance Type.InvokeMember Reflection performance .NET development Object instantiation in .NET Meta Description: Discover the performance differences between Activator.CreateInstance and Type.InvokeMember in .NET Reflection. Learn which method is better suited for different scenarios and optimize your code accordingly. TLDR Summary: Activator.CreateInstance and Type.InvokeMember are two powerful methods in .NET Reflection for dynamically creating object instances. While Activator.CreateInstance excels in scenarios with parameterless constructors, Type.InvokeMember performs better in cases involving constructor parameters or primary constructors. Understand the performance nuances and choose the right method for your .NET development needs.
featured image - Head to Head Performance of Activator.CreateInstance vs Type.InvokeMember
Dev Leader HackerNoon profile picture

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!


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, :

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:

Activator.CreateInstance vs Type.InvokeMember - Benchmarks For Parameterless Constructors


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:

Activator.CreateInstance vs Type.InvokeMember - Benchmarks For Classic Constructors With Parameters


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:

Activator.CreateInstance vs Type.InvokeMember - Benchmarks For Primary Constructors


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!


Also published here.