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
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
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
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
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
| Method | Relative Speed | Compile-Time Type Needed | Caching Required |
new T() | 1x (baseline) | Yes | No |
| Compiled Expression | ~1.2x | No (cached) | Yes |
| Cached ConstructorInfo | ~5x | No | Yes |
Activator.CreateInstance | ~25x | No | No |
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 cachedFunc<T>. Always store the compiled delegate in a static field orConcurrentDictionary. - Using
Activator.CreateInstancein hot loops: A single call toActivator.CreateInstanceis 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
ConcurrentDictionaryinstead ofDictionary. 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 withExpression.New(constructorInfo, parameterExpressions)and match parameter types correctly. - Assuming
new T()with a generic constraint is always fast: Thenew()constraint generates a call toActivator.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.CreateInstanceis convenient but 10-50x slower than direct construction- Compiled lambda expressions (
Expression.New+.Compile()) are nearly as fast asnewwhen cached - Cache
ConstructorInfofor a simpler 2-5x speedup overActivator.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
ConcurrentDictionaryfor thread-safe caching of compiled factories

