The typical way to create an object in .NET/C# is to use the new
keyword. However it's also possible to create a new instance of an object using reflection. In this post I compare 4 different methods, and benchmark them to see which is fastest.
Creating objects using reflection—why bother?
In the Datadog APM tracer library we automatically instrument multiple libraries in your application. This allows us to add distributed tracing to your application without you having to change your code at all.
To achieve this, we often have to interact with library types without having a direct reference to them. For example, I was recently working on our Kafka integration to automatically instrument Confluent's .NET Client for Kafka. I needed to be able to create an instance of the Headers
class.
In an application, the solution would be simple—reference the Confluent.Kafka NuGet package, and call new Headers()
, but we can't do that in the Tracer. If we reference a NuGet package, then we would need to reference that package when we instrument your application. But what if you already reference some version of Confluent.Kafka? On .NET Framework we would likely run into binding redirect problems, while on .NET Core/.NET 5 we could cause MissingMethodException
s or any number of other problems.
To avoid all this, we use reflection to interact with the Confluent.Kafka types, using whichever version of the library you have referenced in your application. This avoids the multiple-references issue (though obviously means we need to be careful about ensuring we test with many different versions of the library).
Reflection is hugely flexible, but the big downside is that it's slow compared to directly interacting with objects. For a performance-critical application like the APM tracer, we need to make sure we're being as performant as possible, so we ensure that the reflection we do is highly optimised.
Which brings me back to the case in point. I needed a way to call new Headers()
without referencing the Headers
type directly. There are actually multiple ways to do this using reflection, which I will walk through here. At the end of the post, I run a benchmark to compare them, to see which is fastest.
4 ways to create an object using reflection
There are quite possibly more, but I've come up with 4 ways to create an object using reflection:
- Calling
Invoke
on aConstructorInfo
instance. - Using
Activator.CreateInstance()
. - Creating a Compiled
Expression
. - Using
DynamicMethod
andReflection.Emit
.
These are arranged in roughly ascending order of complexity. I'll walk through each approach in the following sections before we get to the benchmarks themselves.
1. Standard Reflection using Invoke
The first approach is the "traditional" reflection approach:
Type typeToCreate = typeof(Headers);
ConstructorInfor ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);
object headers = ctor.Invoke(null);
The first step is to obtain a Type
representing the type you want to create. In this case (and in all the other examples) I've used typeof(Headers)
for simplicity, but this relies on having a direct reference to the Headers
type, so in practice, you'd obtain the same Type
through some other mechanism.
From this Type
you can get a reference to the ConstructorInfo
, which describes the parameterless constructor for the Headers
type. The EmptyTypes
helper is a cached empty array (new Type[0]
), which is used to indicate you want the parameterless constructor.
Finally, you can execute the constructor by calling Invoke()
on the ConstructorInfo
and passing in null
(as the constructor does not take any arguments). This returns an object
which is actually an instance of Headers
.
This is one of the simplest and most flexible ways to use reflection, as you can use similar approaches to invoke methods on an object, access fields, interfaces and attributes etc. However, as you'll see later, it's also one of the slowest. The next approach is a slightly optimised approach designed specifically for our scenario - construction.
2. Activator.CreateInstance
In this post I'm looking at a single scenario - creating an instance of an object. There happens to be a helper class designed specifically for this available in both .NET Framework and .NET Core called Activator
. You can use this class to easily create an instance of a type using the following:
Type typeToCreate = typeof(Headers);
object headers = Activator.CreateInstance(typeToCreate);
as before, we need a reference to the Headers
type, but then we can simply call Activator.CreateInstance(type)
. No need to mess around with ConstructorInfo
or anything. Very handy! As with the previous method, you can call parameterised constructors too if you need to.
There's not much more to say about this one, so we'll move on to the next one, where things start to get more interesting.
3. Compiled expressions
Expressions have been around for a long time (since C# 3.0) and are integral to various other features and libraries such as LINQ and ORMs such as EF Core. In many ways they are similar to reflection, in that they allow manipulation of code at runtime,
Expressions offer a high-ish level language for declaring code, which can subsequently be converted into an executable Func<>
by calling Compile
. We can create an expression that creates an instance of the Headers
type, compile it into a Func<object>
, and then invoke it as follows:
NewExpression constructorExpression = Expression.New(HeadersType);
Expression<Func<object>> lambdaExpression = Expression.Lambda<Func<object>>(constructoeExpression);
Func<object> createHeadersFunc = lambdaExpression.Compile();
object Headers = createHeadersFunc();
The first two lines of this snippet create an expression that is equivalent to () => new Headers()
. The third line converts the Expression<>
into a Func<>
that we can execute. The final line invokes our newly created Func<object>
to create the Headers
object.
For this simple example of calling a constructor, the syntax is pretty easy to understand, especially as you've likely worked with Expression
s in the past. In contrast, the final approach in this post, using Reflection.Emit, may not be something you've used before (I hadn't!).
4. Reflection.Emit
Reflection.Emit refers to the System.Reflection.Emit namespace, which contains various methods for creating new intermediate language (IL) in your application. IL instructions are the "assembly code" that the compiler outputs when you compile your application. The JIT in the .NET runtime converts these IL instructions into real assembly code when your application runs.
The approach in this section uses a class called DynamicMethod
to create a new method in the assembly at runtime, with the IL method body that creates an instance of the Headers
object.
Effectively, we're dynamically creating a method that looks like this:
Headers KafkaDynamicMethodHeaders()
{
return new Headers();
}
The following snippet creates a method signature similar to this using DynamicMethod
, though it doesn't have a method body yet;
Type headersType = typeof(Headers);
DynamicMethod createHeadersMethod = new DynamicMethod(
name: $"KafkaDynamicMethodHeaders",
returnType: headersType,
parametertypes: null,
module: typeof(ConstructorBenchmarks).Module,
skipVisibility: false);
We create a DynamicMethod
providing the name
of the method, the parameter and return Type
s, and the Module
in which the method should be defined. Finally we specify whether the JIT visibility checks should be skipped for types and members accessed by the IL of the dynamic method. I'm only accessing public
types here, so chose to not skip it.
Type
s are defined inModule
s. This is very similar to anAssembly
, but a singleAssembly
can contain multipleModule
s.
We have the method signature, now we need to create the method body's IL:
ConstructorInfor ctor = typeToCreate.GetConstructor(System.Type.EmptyTypes);
ILGenerator il = createHeadersMethod.GetILGenerator();
il.Emit(OpCodes.Newobj, Ctor);
il.Emit(OpCodes.Ret);
We obtain an ILGenerator
for the method by calling GetILGenerator()
. We can then Emit()
IL operation codes. This is essentially hand-crafting the "raw" IL codes that the original KafkaDynamicMethodHeaders
would create.
One way to work out what IL you need is to write the C# you need, and then look at the generated IL.
IL_0000: newobj instance void Headers::.ctor()
IL_0005: ret
The final step to create the dynamic method is to create a delegate that we can use to execute the function. We'll use a Func<object>
again, which we can invoke to create the Headers
object:
Func<object> headersDelegate = createHeadersMethod.CreateDelegate(typeof(Func<object>));
object headers = headersDelegate();
That covers the 4 approaches to calling a constructor using reflection, now on to the benchmarks!
Benchmarking the approaches
To run the benchmarks, I used the excellent BenchmarkDotNet library and created the following benchmark. This uses the new Header()
case as the "baseline", and compares each of the other approaches. All of the one-off work of loading Type
s, compiling expressions, and generating DynamicMethod
s is done in the constructor, so it's not included in the startup time. We're measuring the "steady state" time to create the Headers
instance.
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet.Attributes;
using Confluent.Kafka;
public class ConstructorBenchmarks
{
private static readonly Type HeadersType = typeof(Headers);
private static readonly ConstructorInfo Ctor = HeadersType.GetConstructor(System.Type.EmptyTypes);
private readonly Func<object> _dynamicMethodActivator;
private readonly Func<object> _dynamicMethodActivator2;
private readonly Func<object> _expression;
public ConstructorBenchmarks()
{
DynamicMethod createHeadersMethod = new DynamicMethod(
$"KafkaDynamicMethodHeaders",
HeadersType,
null,
typeof(ConstructorBenchmarks).Module,
false);
ILGenerator il = createHeadersMethod.GetILGenerator();
il.Emit(OpCodes.Newobj, Ctor);
il.Emit(OpCodes.Ret);
_dynamicMethodActivator = (Func<object>)createHeadersMethod.CreateDelegate(typeof(Func<object>));
_expression = Expression.Lambda<Func<object>>(Expression.New(HeadersType)).Compile();
}
[Benchmark(Baseline = true)]
public object Direct() => new Headers();
[Benchmark]
public object Reflection() => Ctor.Invoke(null);
[Benchmark]
public object ActivatorCreateInstance() => Activator.CreateInstance(HeadersType);
[Benchmark]
public object CompiledExpression() => _expression();
[Benchmark]
public object ReflectionEmit() => _dynamicMethodActivator();
}
Using C# 9.0's top-level statements, the Program.cs file is as simple as:
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<ConstructorBenchmarks>();
For completeness, the project file is shown below. I decided to test .NET Framework and .NET 5 separately.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net461;net5.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Confluent.Kafka" Version="1.6.3" />
</ItemGroup>
</Project>
To run the benchmarks I used dotnet run -c Release --framework net5.0
for example.
Finally…1500 words later, what are the results!?
The results
Obviously, don't get too hung up on the real numbers here. I'm using a middle-of-the-road laptop from several years ago, but the absolute numbers are not what I'm interested in. I'm more interested in the relative performance of each approach.
First off, lets look at the .NET 5 numbers:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=5.0.104
[Host] : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT
DefaultJob : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD | |------------------------ |----------:|----------:|----------:|------:|--------:| | Direct | 9.851 ns | 0.1219 ns | 0.1081 ns | 1.00 | 0.00 | | Reflection | 97.489 ns | 0.7870 ns | 0.6977 ns | 9.90 | 0.12 | | ActivatorCreateInstance | 38.092 ns | 0.3416 ns | 0.2853 ns | 3.87 | 0.06 | | CompiledExpression | 11.579 ns | 0.1674 ns | 0.1398 ns | 1.18 | 0.02 | | ReflectionEmit | 12.464 ns | 0.2388 ns | 0.2117 ns | 1.27 | 0.02 |
And the .NET Framework results:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
[Host] : .NET Framework 4.8 (4.8.4300.0), X64 RyuJIT
DefaultJob : .NET Framework 4.8 (4.8.4300.0), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD | |------------------------ |----------:|---------:|---------:|------:|--------:| | Direct | 12.94 ns | 0.133 ns | 0.124 ns | 1.00 | 0.00 | | Reflection | 149.51 ns | 1.027 ns | 1.142 ns | 11.58 | 0.14 | | ActivatorCreateInstance | 66.65 ns | 1.210 ns | 1.073 ns | 5.15 | 0.07 | | CompiledExpression | 21.60 ns | 0.481 ns | 0.515 ns | 1.67 | 0.05 | | ReflectionEmit | 15.30 ns | 0.381 ns | 0.391 ns | 1.18 | 0.04 |
Running the benchmark several times, there's a fair amount of variation in the numbers. Being a laptop, I'd imagine it's possible there was some thermal-throttling at play but the general pattern seems quite stable:
- Standard reflection using
ConstructorInfo.Invoke()
is roughly 10× slower than callingnew Headers()
Activator.CreateInstance
is 2× faster, i.e. roughly 5× slower than callingnew Headers()
- The compiled expression and dynamic method approaches are roughly the same as calling
new Headers()
. For .NET 5, we're talking a couple of nanoseconds difference. That's very impressive! - On .NET Framework the DynamicMethod approach is faster than using compiled expressions, while on .NET 5, compiled expressions appear to be slightly faster than using
DynamicMethod
Given these differences, it seems clear that for performance-sensitive applications, it may well be worth the effort of using compiled expressions or DynamicMethod
, even for this simple case.
For completeness, I decided to also benchmark the startup time for each approach. This is a one-off cost that you pay when first calling a specific dynamic method, but it seems like information worth having. This post is already pretty long, so I'll leave out the code for now, but these are the results I get for .NET 5 when the setup costs are included:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=5.0.104
[Host] : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT
Job-ONVXJR : .NET Core 5.0.5 (CoreCLR 5.0.521.16609, CoreFX 5.0.521.16609), X64 RyuJIT
LaunchCount=50 RunStrategy=ColdStart
| Method | Mean | Error | StdDev | Median | |------------------------ |-------------:|-----------:|-------------:|-------------:| | Reflection | 310.29 ns | 10.195 ns | 218.95 ns | 288.30 ns | | ActivatorCreateInstance | 146.38 ns | 9.178 ns | 197.10 ns | 119.70 ns | | CompiledExpression | 90,413.14 ns | 437.343 ns | 9,221.75 ns | 88,071.55 ns | | ReflectionEmit | 86,897.24 ns | 820.281 ns | 16,968.07 ns | 79,778.60 ns |
As you can see, compiled expressions and Reflection.Emit have a considerable setup cost. This is important to bear in mind: you'll only start to see the benefits of Reflection.Emit's speedup over using Activator.CreateInstance()
after you've called the delegate about 3,500 times! Depending on where you're using this code, that trade off may or may not be worth it!
Summary
In this post I showed 4 different ways to call the constructor of a type using reflection. I then ran benchmarks comparing each approach using BenchmarkDotNet. This shows that using the naïve approach to reflection is about 10× slower than calling the constructor directly, and that using Activator.CreateInstance()
is about 5× slower. Both compiled expressions and DynamicMethod
with Reflection.Emit approach the speed of calling new Headers()
directly, with compiled expressions slightly faster in .NET 5 and ReflectionEmit faster on .NET Framework.