Skip to content

Function pointers C# 9.0performanceinterop

Call methods through pointers for zero-overhead invocation in performance-critical code.

Delegates provide type-safe method references but incur allocation and indirection overhead. For performance-critical scenarios and native interop, this overhead can be significant, but C# lacked a way to work with raw function pointers safely.

C# 9.0 introduces function pointers using the delegate* syntax in unsafe contexts. This provides direct, zero-overhead method invocation without delegate allocations, making it ideal for high-performance scenarios, game engines, and P/Invoke calls.

Code

C#
unsafe class Calculator
{
    public static int Add(int a, int b) => a + b;
    public static int Multiply(int a, int b) => a * b;

    public static int Compute(int x, int y, delegate*<int, int, int> operation)
    {
        return operation(x, y);
    }

    public static void Main()
    {
        delegate*<int, int, int> addPtr = &Add;
        delegate*<int, int, int> mulPtr = &Multiply;

        int sum = Compute(5, 3, addPtr);        // 8
        int product = Compute(5, 3, mulPtr);    // 15

        // Can also call directly
        int result = addPtr(10, 20);            // 30
    }
}

// P/Invoke scenario
[DllImport("native.dll")]
static extern void ProcessCallback(delegate* unmanaged[Cdecl]<int, void> callback);
C#
class Calculator
{
    public static int Add(int a, int b) => a + b;
    public static int Multiply(int a, int b) => a * b;

    public static int Compute(int x, int y, Func<int, int, int> operation)
    {
        return operation(x, y); // Delegate invocation overhead
    }

    public static void Main()
    {
        Func<int, int, int> addFunc = Add;     // Allocation
        Func<int, int, int> mulFunc = Multiply; // Allocation

        int sum = Compute(5, 3, addFunc);
        int product = Compute(5, 3, mulFunc);
    }
}

// P/Invoke required marshaling delegates
[DllImport("native.dll")]
static extern void ProcessCallback([MarshalAs(UnmanagedType.FunctionPtr)] Func<int, int> callback);

Notes

  • Function pointers can only be used in unsafe contexts
  • Use the & operator to obtain a pointer to a static method
  • Syntax: delegate*<param1Type, param2Type, ..., returnType>
  • Supports calling conventions: delegate* unmanaged[Cdecl]<...>, delegate* unmanaged[Stdcall]<...>, etc.
  • No allocations or GC pressure - significantly faster than delegates for repeated calls
  • Cannot point to instance methods directly (must be static or use function pointers with explicit this parameters)
  • Primarily useful for interop, hot paths in performance-critical code, and game engines
  • Unlike delegates, function pointers are not type-safe at the language level and require unsafe code

More information