What’s faster ? Ep. 1. Instantiating objects in .NET

Introduction

Ariel Amor Garcia
4 min readMar 13, 2023

When working with high level object oriented programming languages like C# we tend to forget on how costly in terms of CPU usage or memory consumption is our code. We often do not write applications that require super high performant code, but from time to time we may face situations where we need to optimize memory consumption or CPU usage.

This, besides my first post on the platform, is the starting point of a series where I’ll try to cover multiple ways of facing a certain situation where performance or efficiency can be improved, make some benchmarking and write some conclusions. These posts will be short texts where anybody can quickly find information about the topic.

Implementation and benchmarks

So, let’s dive in our scenario. In C# we have multiple ways of instantiating our objects. For our tests we will be creating instances of this C# class.

public class TestClass
{
public TestClass(int parameter1, string parameter2)
{
}
}

Sticking to the classics the most common way of instantiating objects in C# is using the new keyword. This is the first benchmark we will be executing.

[Benchmark]
public TestClass DirectlyCallingConstructor() => new TestClass(integerValue, stringValue);

In situations where we have to create instances of generic types where we know constructor parameters we can use Activator.CreateInstance method.

[Benchmark]
public void CallActivatorWithType() => Activator.CreateInstance(typeof(TestClass), integerValue, stringValue);

When running these two benchmarks, the results we obtain are these results.

|                  Method |       Mean |     Error |    StdDev |     Median | Ratio | RatioSD |   Gen0 | Allocated | Alloc Ratio |
|------------------------ |-----------:|----------:|----------:|-----------:|------:|--------:|-------:|----------:|------------:|
| DirectlyCallConstructor | 5.664 ns | 0.2286 ns | 0.6597 ns | 5.485 ns | 1.00 | 0.00 | 0.0038 | 24 B | 1.00 |
| CallActivatorWithType | 237.905 ns | 4.5727 ns | 5.6157 ns | 237.418 ns | 41.55 | 4.71 | 0.0520 | 328 B | 13.67 |

Calling Activator.CreateInstance is much heavier operation than calling directly the constructor of a class. It seems easy to understand why this happens, reflection is slow, but we can’t always access directly the constructor of a class but we want to instantiate objects using reflection to create instances of generic type parameters.

Is there any way to create instances using reflection that reduces this performance overhead Activator.CreateInstance causes ? Yes, the answer are precompiled lambdas. Lambda expressions are anonymous functions we all (.NET developers) are familiar with because they are widely used for example to iterate over collections using Linq.

Here is how we can using reflection and creating an expression tree we can create a Lambda expression to instantiate our class. Note that this will only work with constructors of two parameters, more overloads must be implemented to support a wider variety of constructor parameters.

static Func<TArg1, TArg2, T> CreateCreator<TArg1, TArg2, T>()
{
var constructor = typeof(T).GetConstructor(new Type[] { typeof(TArg1), typeof(TArg2) });
var parameter = Expression.Parameter(typeof(TArg1), "p1");
var parameter2 = Expression.Parameter(typeof(TArg2), "p2");

var creatorExpression = Expression.Lambda<Func<TArg1, TArg2, T>>(
Expression.New(constructor, new Expression[] { parameter, parameter2 }), parameter, parameter2);
return creatorExpression.Compile();
}

The benchmark (note that compiled lambda expressions are cached and not built and compiled in each benchmark execution):

[Benchmark]
public TestClass PrecompiledLamdba() => objectActivator(integerValue, stringValue);

When running all the benchmarks these are the results :

|                  Method |       Mean |      Error |     StdDev |     Median | Ratio | RatioSD |   Gen0 | Allocated | Alloc Ratio |
|------------------------ |-----------:|-----------:|-----------:|-----------:|------:|--------:|-------:|----------:|------------:|
| DirectlyCallConstructor | 4.933 ns | 0.3594 ns | 1.0598 ns | 4.344 ns | 1.00 | 0.00 | 0.0038 | 24 B | 1.00 |
| CallActivatorWithType | 233.254 ns | 10.6189 ns | 30.1240 ns | 225.604 ns | 48.29 | 9.87 | 0.0522 | 328 B | 13.67 |
| PrecompiledLambda | 4.162 ns | 0.1359 ns | 0.2926 ns | 4.161 ns | 0.79 | 0.18 | 0.0038 | 24 B | 1.00 |

Calling the precompiled cached lambda seems to be even faster than calling the constructor and as efficient in terms of memory allocation than calling the constructor (24B).

Conclusions

While these kinds of optimizations may not have a significant impact on application performance, they can still be valuable tools for improving efficiency. But! one of my goals with these post series is to show that very efficient code can be written in C# and this kind of exercises will show us how we can optimize hot code paths that might have some impact. We have many very well engineered tools in .NET environment that can help us achieve this task.

Regarding the real topic of the post, do not use Activator, in most of the cases you can switch to compile lambda expressions and cache these instances and use them instead of calling Activator.

See you next time!

--

--

Ariel Amor Garcia
Ariel Amor Garcia

Written by Ariel Amor Garcia

Software Developer working for Lidl International Hub in Barcelona. I'm from Spain and I like motorbikes ;)