Comparing Activator.CreateInstance and Type.InvokeMember for Dynamic Object Instantiation

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.CreateInstance outperforms Type.InvokeMember significently, roughly an order of magnitude faster.
  • Single String Constructor (class): Both methods perform closely; Activator.CreateInstance holds a marginal lead.
  • Primary Constructor (record-like): Type.InvokeMember edges 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.

Tags: C# reflection Activator.CreateInstance Type.InvokeMember Performance

Posted on Mon, 25 May 2026 20:30:17 +0000 by keziah