Activator.CreateInstance vs Type.InvokeMember – A Clear Winner?

devleader

Dev Leader

Posted on March 21, 2024

Activator.CreateInstance vs Type.InvokeMember – A Clear Winner?

The post Activator.CreateInstance vs Type.InvokeMember – A Clear Winner? appeared first on Dev Leader.

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.

Dev Leader Weekly | Substack

My weekly newsletter that simplifies software engineering for you, along with C# code examples. Join thousands of software engineers from companies like Microsoft and Amazon who are already reading! Click to read Dev Leader Weekly, a Substack publication with thousands of subscribers.

favicon weekly.devleader.ca

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.CreateInstancegenerally 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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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!
    });
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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!",
    });
Enter fullscreen mode Exit fullscreen mode

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)
{
}
Enter fullscreen mode Exit fullscreen mode

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!",
                });
    }
}
Enter fullscreen mode Exit fullscreen mode

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 I’ll publish on Sunday March 17th. Stay tuned!

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!

Dev Leader Weekly | Substack

My weekly newsletter that simplifies software engineering for you, along with C# code examples. Join thousands of software engineers from companies like Microsoft and Amazon who are already reading! Click to read Dev Leader Weekly, a Substack publication with thousands of subscribers.

favicon weekly.devleader.ca

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
💖 💪 🙅 🚩
devleader
Dev Leader

Posted on March 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related