Activator
CreateInstance
Performance Optimization
.NET
C#

Activator.CreateInstance Performance Alternative

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Activator.CreateInstance uses reflection to instantiate types at runtime, which is convenient but slow — roughly 5-50x slower than direct construction depending on the scenario. For hot paths in dependency injection containers, serializers, and object pools, this overhead matters. Faster alternatives include compiled lambda expressions, Expression.New, cached ConstructorInfo.Invoke, and source generators. Each approach trades compile-time knowledge for runtime flexibility at different performance levels.

Baseline: Activator.CreateInstance

csharp
1using System;
2
3public class MyService
4{
5    public string Name { get; set; } = "Default";
6}
7
8// Runtime instantiation — uses reflection internally
9object instance = Activator.CreateInstance(typeof(MyService));
10
11// Generic version — slightly cleaner syntax
12MyService service = Activator.CreateInstance<MyService>();
13
14// With constructor parameters
15var withArgs = (MyService)Activator.CreateInstance(typeof(MyService), "Custom");

Activator.CreateInstance locates the constructor via reflection on every call. There is no caching — each invocation pays the full reflection cost.

Alternative 1: Compiled Lambda Expression

csharp
1using System;
2using System.Linq.Expressions;
3
4public static class FastActivator<T> where T : new()
5{
6    // Compile once, reuse many times
7    private static readonly Func<T> Factory =
8        Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
9
10    public static T Create() => Factory();
11}
12
13// Usage — near-native speed after first call
14MyService service = FastActivator<MyService>.Create();
15
16// Non-generic version for runtime types
17public static class FastActivator
18{
19    private static readonly Dictionary<Type, Func<object>> Cache = new();
20
21    public static object Create(Type type)
22    {
23        if (!Cache.TryGetValue(type, out var factory))
24        {
25            var expr = Expression.Lambda<Func<object>>(
26                Expression.Convert(Expression.New(type), typeof(object))
27            );
28            factory = expr.Compile();
29            Cache[type] = factory;
30        }
31        return factory();
32    }
33}

The expression tree is compiled to IL once and cached. Subsequent calls invoke the compiled delegate directly — typically 10-50x faster than Activator.CreateInstance in benchmarks.

Alternative 2: Cached ConstructorInfo

csharp
1using System;
2using System.Collections.Concurrent;
3using System.Reflection;
4
5public static class CachedActivator
6{
7    private static readonly ConcurrentDictionary<Type, ConstructorInfo> CtorCache = new();
8
9    public static object Create(Type type)
10    {
11        var ctor = CtorCache.GetOrAdd(type, t =>
12            t.GetConstructor(Type.EmptyTypes)
13            ?? throw new InvalidOperationException($"No parameterless constructor on {t}"));
14
15        return ctor.Invoke(null);
16    }
17
18    // With parameters
19    public static object Create(Type type, params object[] args)
20    {
21        var argTypes = Array.ConvertAll(args, a => a.GetType());
22        var ctor = type.GetConstructor(argTypes)
23            ?? throw new InvalidOperationException("No matching constructor");
24        return ctor.Invoke(args);
25    }
26}

Caching ConstructorInfo avoids the repeated type lookup but still uses reflection for the actual invocation. This is 2-5x faster than Activator.CreateInstance — a good middle ground when compiled expressions are too complex.

Alternative 3: Generic new() Constraint

csharp
1// When the type is known at compile time via generics
2public T CreateInstance<T>() where T : new()
3{
4    return new T();  // JIT compiles to direct constructor call
5}
6
7// In a factory pattern
8public interface IFactory<T>
9{
10    T Create();
11}
12
13public class DefaultFactory<T> : IFactory<T> where T : new()
14{
15    public T Create() => new T();
16}
17
18// Usage
19var factory = new DefaultFactory<MyService>();
20MyService service = factory.Create();  // no reflection

The new() constraint lets the JIT compiler emit a direct constructor call. This is the fastest option but requires the type to be known at compile time through generics.

Performance Comparison

csharp
1using BenchmarkDotNet.Attributes;
2
3[MemoryDiagnoser]
4public class ActivatorBenchmarks
5{
6    [Benchmark(Baseline = true)]
7    public MyService DirectNew() => new MyService();
8
9    [Benchmark]
10    public object ActivatorCreate() => Activator.CreateInstance(typeof(MyService));
11
12    [Benchmark]
13    public MyService CompiledLambda() => FastActivator<MyService>.Create();
14
15    [Benchmark]
16    public object CachedCtor() => CachedActivator.Create(typeof(MyService));
17}
18
19// Typical results (relative to DirectNew):
20// DirectNew:         1.0x  (~2 ns)
21// CompiledLambda:    1.2x  (~2.5 ns)
22// CachedCtor:        5x    (~10 ns)
23// ActivatorCreate:   25x   (~50 ns)
MethodRelative SpeedCompile-Time Type NeededCaching Required
new T()1x (baseline)YesNo
Compiled Expression~1.2xNo (cached)Yes
Cached ConstructorInfo~5xNoYes
Activator.CreateInstance~25xNoNo

Common Pitfalls

  • Not caching the compiled delegate: Compiling an expression tree on every call is far slower than Activator.CreateInstance. The speed benefit only comes from compiling once and reusing the cached Func<T>. Always store the compiled delegate in a static field or ConcurrentDictionary.
  • Using Activator.CreateInstance in hot loops: A single call to Activator.CreateInstance is fast enough to be negligible, but calling it thousands of times per second (e.g., in a DI container or serializer) creates measurable overhead. Profile before optimizing — only switch to compiled expressions if profiling shows reflection as a bottleneck.
  • Forgetting thread safety for the cache: If multiple threads access the factory cache simultaneously, use ConcurrentDictionary instead of Dictionary. A regular dictionary can corrupt its internal state under concurrent writes.
  • Ignoring constructor parameters: Expression.New(typeof(T)) only works for parameterless constructors. For types with constructor parameters, build the expression tree with Expression.New(constructorInfo, parameterExpressions) and match parameter types correctly.
  • Assuming new T() with a generic constraint is always fast: The new() constraint generates a call to Activator.CreateInstance<T>() in some older .NET runtimes. In .NET 6+ the JIT optimizes this to a direct constructor call, but on .NET Framework it may still use reflection internally.

Summary

  • Activator.CreateInstance is convenient but 10-50x slower than direct construction
  • Compiled lambda expressions (Expression.New + .Compile()) are nearly as fast as new when cached
  • Cache ConstructorInfo for a simpler 2-5x speedup over Activator.CreateInstance
  • Use new() generic constraints when the type is known at compile time
  • Always profile first — only optimize if reflection is a measured bottleneck
  • Use ConcurrentDictionary for thread-safe caching of compiled factories

Course illustration
Course illustration

All Rights Reserved.