Dynamic Instance Creation via Activator.CreateInstance
Activator.CreateInstance enables instantiation of types at runtime without compile-time knowledge. It belongs to System.Activator and suits scenarios such as plugin loading or assembly resolution where the concrete type is determined dynamically.
Benefits
- Supports late-bound creation when the type is resolved during execution.
- Reduces boilerplate conditional logic for multiple possible types.
- Enhances extensibility in systems requiring runtime type resolution.
Trade-offs
- Incurs reflection overhead, making it slower then direct construction.
- Often requires a public parameterless constructor unless arguments are explicitly supplied.
- Lacks compile-time verification, increasing risk of mismatches after type changes.
Usage Examples
// Known type instantiation
Type t = typeof(WorkerProcessor);
object obj = Activator.CreateInstance(t);
// Runtime-known type from name
string typeId = "Domain.Services.WorkerProcessor";
Type located = Type.GetType(typeId);
object dynamicObj = Activator.CreateInstance(located);
// Supplying constructor arguments
string typeId = "Domain.Services.WorkerProcessor";
Type located = Type.GetType(typeId);
object configured = Activator.CreateInstance(
located,
new object[] { "init data" }
);
Dynamic Instance Creation via Type.InvokeMember
Type.InvokeMember offers another reflection-based pathway for constructing objects. While also enabling late binding, its design supports broader member operations beyond constructors, using binding flags to specify invocation intent.
Benefits
- Facilitates late-bound creation similar to
Activator.CreateInstance. - Consolidates multiple reflection tasks under one API.
- Adaptable to varied member interactions, not limited to construction.
Trade-offs
- Slightly more verbose due to additional parameters and flag configuration.
- Similar performance profile but may vary depending on constructor signature.
- No compile-time safety for argument compatibility.
Usage Examples
// Known type instantiation
Type t = typeof(WorkerProcessor);
object obj = t.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
null
);
// Runtime-known type from name
string typeId = "Domain.Services.WorkerProcessor";
Type located = Type.GetType(typeId);
object dynamicObj = located.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
null
);
// Constructor with parameters
string typeId = "Domain.Services.WorkerProcessor";
Type located = Type.GetType(typeId);
object configured = located.InvokeMember(
null,
BindingFlags.CreateInstance,
null,
null,
new object[] { "init data" }
);
Performance Comparison Using BenchmarkDotNet
Three test categories were measured: parameterless constructors, classic single-string constructors, and primary constructor with a string parameter.
Test Types
public class EmptyCreator { }
public class LegacyCtor
{
readonly string _data;
public LegacyCtor(string data) => _data = data;
}
public class RecordLike(string data) { }
Benchmark Fixtures
[ShortRunJob]
public class EmptyCreatorBench
{
private Type? _t;
[GlobalSetup] public void Setup() => _t = typeof(EmptyCreator);
[Benchmark] public object Direct() => new EmptyCreator();
[Benchmark(Baseline = true)] public object ViaActivator() => Activator.CreateInstance(_t!);
[Benchmark] public object ViaInvoke() => _t!.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
}
[ShortRunJob]
public class LegacyCtorBench
{
private Type? _t;
[GlobalSetup] public void Setup() => _t = typeof(LegacyCtor);
[Benchmark] public object Direct() => new LegacyCtor("demo");
[Benchmark(Baseline = true)] public object ViaActivator() => Activator.CreateInstance(_t!, new object[] { "demo" });
[Benchmark] public object ViaInvoke() => _t!.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { "demo" });
}
[ShortRunJob]
public class RecordLikeBench
{
private Type? _t;
[GlobalSetup] public void Setup() => _t = typeof(RecordLike);
[Benchmark] public object Direct() => new RecordLike("demo");
[Benchmark(Baseline = true)] public object ViaActivator() => Activator.CreateInstance(_t!, new object[] { "demo" });
[Benchmark] public object ViaInvoke() => _t!.InvokeMember(null, BindingFlags.CreateInstance, null, null, new object[] { "demo" });
}
Results Summary
- Parameterless Constructor:
Activator.CreateInstanceoutperformsType.InvokeMembersignificently, roughly an order of magnitude faster. - Single String Constructor (class): Both methods perform closely;
Activator.CreateInstanceholds a marginal lead. - Primary Constructor (record-like):
Type.InvokeMemberedges ahead slightly, contrasting earlier cases.
Functional Distinctions
While both mechanisms enable dynamic instantiation, Activator.CreateInstance is purpose-built for object creation and yields better throughput for parameterless cases. Type.InvokeMember provides a generalized interface for various member invocations, which can be advantageous in complex reflection workflows but may introduce minor overhead. With parameterized constructors, especially modern primary forms, performance differances narrow and may favor Type.InvokeMember in specific runtime contexts.