Reflection
C#
.NET
programming
method invocation

Difference Between Invoke and DynamicInvoke

Master System Design with Codemia

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


## Introduction

In .NET, delegates are type-safe function pointers that let you pass methods around as objects. When you need to call the method a delegate points to, you have two options: `Invoke` and `DynamicInvoke`. They both execute the underlying method, but they differ significantly in type safety, performance, and use cases. Understanding when to use each one helps you write faster and more maintainable code.

This article explains both methods with working C# examples, benchmarks the performance difference, and covers practical scenarios where each one is appropriate.

## How Invoke Works

`Invoke` is the standard, type-safe way to call a delegate. The compiler generates the `Invoke` method on every delegate type, and it has the same signature as the delegate itself. This means argument types are checked at compile time.

```csharp
using System;

// Define a delegate type
delegate int MathOperation(int a, int b);

class Program
{
    static int Add(int a, int b) => a + b;
    static int Multiply(int a, int b) => a * b;

    static void Main()
    {
        MathOperation op = Add;

        // Calling Invoke explicitly
        int result = op.Invoke(3, 4);
        Console.WriteLine(result);  // 7

        // Shorthand syntax (compiler calls Invoke behind the scenes)
        result = op(5, 6);
        Console.WriteLine(result);  // 11
    }
}
```

When you write `op(5, 6)`, the compiler translates that to `op.Invoke(5, 6)`. There is no runtime overhead for type checking because the compiler already verified that `5` and `6` are valid `int` arguments.

## How DynamicInvoke Works

`DynamicInvoke` is defined on the base `Delegate` class and accepts arguments as `object[]`. It performs type checking and argument conversion at runtime using reflection.

```csharp
using System;

delegate int MathOperation(int a, int b);

class Program
{
    static int Add(int a, int b) => a + b;

    static void Main()
    {
        MathOperation op = Add;

        // Using DynamicInvoke
        object result = op.DynamicInvoke(new object[] { 3, 4 });
        Console.WriteLine(result);  // 7

        // Arguments are boxed and unboxed at runtime
        int typedResult = (int)op.DynamicInvoke(new object[] { 10, 20 });
        Console.WriteLine(typedResult);  // 30
    }
}
```

Because the arguments are passed as `object[]`, value types like `int` must be boxed (wrapped in an object) and then unboxed inside the method. The return value is also boxed into an `object`.

## Performance Comparison

The performance difference between `Invoke` and `DynamicInvoke` is substantial. Here is a simple benchmark.

```csharp
using System;
using System.Diagnostics;

delegate int MathOp(int a, int b);

class Benchmark
{
    static int Add(int a, int b) => a + b;

    static void Main()
    {
        MathOp op = Add;
        int iterations = 1_000_000;

        // Benchmark Invoke
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            op.Invoke(i, i);
        }
        sw.Stop();
        Console.WriteLine($"Invoke: {sw.ElapsedMilliseconds} ms");

        // Benchmark DynamicInvoke
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            op.DynamicInvoke(i, i);
        }
        sw.Stop();
        Console.WriteLine($"DynamicInvoke: {sw.ElapsedMilliseconds} ms");
    }
}
```

Typical results on modern hardware show `DynamicInvoke` running 50x to 100x slower than `Invoke`. The overhead comes from reflection-based argument validation, boxing of value types, and the lack of JIT compiler optimizations that apply to direct calls.

## When to Use Each

### Use Invoke when:

- You know the delegate type at compile time.
- You need maximum performance.
- You are working with events, callbacks, or LINQ expressions in standard application code.

### Use DynamicInvoke when:

- You only have a reference to the base `Delegate` type and do not know the concrete delegate type at compile time.
- You are building a plugin system or scripting engine where method signatures are determined at runtime.
- You are writing framework-level code that needs to invoke arbitrary delegates passed by the user.

## A Practical DynamicInvoke Scenario

Here is a realistic example: an event dispatcher that stores delegates of different types and invokes them dynamically.

```csharp
using System;
using System.Collections.Generic;

class EventDispatcher
{
    private Dictionary<string, Delegate> handlers = new();

    public void Register(string eventName, Delegate handler)
    {
        handlers[eventName] = handler;
    }

    public object Dispatch(string eventName, params object[] args)
    {
        if (handlers.TryGetValue(eventName, out var handler))
        {
            return handler.DynamicInvoke(args);
        }
        throw new InvalidOperationException($"No handler for {eventName}");
    }
}

class Program
{
    static void Main()
    {
        var dispatcher = new EventDispatcher();

        // Register handlers with different signatures
        dispatcher.Register("greet", new Func<string, string>(
            name => $"Hello, {name}!"
        ));
        dispatcher.Register("add", new Func<int, int, int>(
            (a, b) => a + b
        ));

        Console.WriteLine(dispatcher.Dispatch("greet", "Alice"));  // Hello, Alice!
        Console.WriteLine(dispatcher.Dispatch("add", 3, 7));        // 10
    }
}
```

In this case, `DynamicInvoke` is necessary because the dispatcher does not know the delegate signatures at compile time. The dictionary stores the base `Delegate` type, so `Invoke` is not available.

## Common Pitfalls

1. **Using DynamicInvoke when Invoke would work.** If you have the concrete delegate type, always prefer `Invoke`. The performance difference is too large to ignore in hot paths.

2. **Swallowing inner exceptions.** `DynamicInvoke` wraps any exception thrown by the target method in a `TargetInvocationException`. If you catch exceptions, remember to unwrap the `InnerException` to get the actual error.

    ```csharp
    try
    {
        op.DynamicInvoke(args);
    }
    catch (System.Reflection.TargetInvocationException ex)
    {
        // The real exception is inside
        Console.WriteLine(ex.InnerException.Message);
    }
    ```

3. **Argument count mismatches.** `DynamicInvoke` throws `TargetParameterCountException` if you pass the wrong number of arguments. Unlike `Invoke`, the compiler cannot catch this at build time.

4. **Boxing overhead with value types.** Every `int`, `bool`, `double`, or struct argument gets boxed when passed through `object[]`. In tight loops, this creates significant GC pressure. If performance matters, find a way to use the typed `Invoke` instead.

## Summary

`Invoke` is the fast, type-safe, compile-time-checked way to call a delegate. `DynamicInvoke` is the flexible, runtime-checked alternative that works when you only have a base `Delegate` reference. In everyday application code, you should almost always use `Invoke` (or its shorthand syntax). Reserve `DynamicInvoke` for framework-level scenarios like plugin systems, event dispatchers, or serialization engines where delegate types are not known until runtime.

Course illustration
Course illustration

All Rights Reserved.