Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A function pointer in C# is a lightweight, strongly-typed, unmanaged pointer to a memory address containing executable code. Introduced in C# 9.0, function pointers provide a mechanism to invoke methods directly via the Intermediate Language (IL) calli instruction, bypassing the heap allocation, object instantiation, and virtual dispatch overhead associated with traditional System.Delegate types. Because they deal directly with memory addresses, function pointers can only be declared and used within an unsafe context.

Syntax and Declaration

Function pointers are declared using the delegate* keyword, followed by type parameters enclosed in angle brackets. The type parameters specify the method signature, where the final type parameter always represents the return type.
// Syntax: delegate*<[ParameterTypes...], [ReturnType]>

// A pointer to a method that takes an int and a string, and returns a bool
delegate*<int, string, bool> managedPointer;

// A pointer to a method that takes no parameters and returns void
delegate*<void> voidPointer;

Assignment and Invocation

To assign a method to a function pointer, you use the address-of operator (&) followed by the method name. Function pointers can point to both static and instance methods. While function pointers do not capture state (closures) or maintain an object reference like delegates, an instance method can be targeted by including the instance’s type as the first parameter in the function pointer’s signature. This explicitly represents the hidden this pointer required by instance methods.
public unsafe class FunctionPointerDemonstration
{
    private static int Add(int x, int y) => x + y;
    private int Multiply(int x, int y) => x * y;

    public void Execute()
    {
        // 1. Static method assignment and invocation
        delegate*<int, int, int> staticPtr = &Add;
        int sum = staticPtr(10, 20); 

        // 2. Instance method assignment and invocation
        // The first type parameter represents the instance ('this')
        delegate*<FunctionPointerDemonstration, int, int, int> instancePtr = &FunctionPointerDemonstration.Multiply;
        int product = instancePtr(this, 10, 20);
    }
}

Managed vs. Unmanaged Function Pointers

Function pointers are categorized by their calling convention, which dictates how arguments are passed to the function and how the stack is cleaned up. 1. Managed Function Pointers By default, a delegate* uses the standard .NET managed calling convention. It can only point to managed C# methods. 2. Unmanaged Function Pointers To point to unmanaged code (e.g., native C/C++ libraries), you must specify the unmanaged keyword. You can optionally define the specific unmanaged calling convention using a bracketed syntax. If the brackets are omitted, the runtime uses the default platform calling convention. Mutually exclusive calling conventions (like Cdecl and Stdcall) cannot be combined. Multiple types within the bracket syntax are strictly reserved for combining a single calling convention with specific compiler modifiers. When using the bracket syntax, the C# compiler automatically prepends the CallConv prefix to the identifiers you specify. Therefore, while the underlying type in the System.Runtime.CompilerServices namespace might be named CallConvSuppressGCTransition, the CallConv prefix must be strictly omitted in the C# language syntax to avoid compiler errors (e.g., CS8892).
using System.Runtime.CompilerServices;

// Unmanaged pointer using the default platform calling convention
delegate* unmanaged<int, void> defaultUnmanagedPtr;

// Unmanaged pointer explicitly specifying the Cdecl calling convention
delegate* unmanaged[Cdecl]<int, void> cdeclPtr;

// Unmanaged pointer explicitly specifying the Stdcall calling convention
delegate* unmanaged[Stdcall]<int, void> stdcallPtr;

// Unmanaged pointer combining a calling convention with a GC transition modifier.
// Note the omission of the "CallConv" prefix for SuppressGCTransition.
delegate* unmanaged[Stdcall, SuppressGCTransition]<int, void> optimizedPtr;

Architectural Differences from Delegates

To understand function pointers technically, it is necessary to contrast them with System.Delegate:
  • Memory Allocation: A Delegate is a reference type that requires heap allocation. A delegate* is a raw memory address (an unmanaged pointer type), requiring zero heap allocation.
  • Invocation Mechanism: Invoking a Delegate requires a virtual method call (callvirt) to the delegate’s Invoke method, which then resolves the target. Invoking a delegate* emits the calli (Call Indirect) IL instruction, jumping directly to the memory address.
  • State: A Delegate maintains an invocation list and an object reference (Target) for instance methods. A delegate* holds no state, no closure, and no object reference.
  • Type System: delegate* types are not derived from System.Object. They cannot be boxed, cannot be used as generic type arguments (e.g., List<delegate*<void>> is invalid), and cannot be used in reflection without specialized metadata handling.
Master C# with Deep Grasping Methodology!Learn More