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