C# 7 Fixed Pattern


In C# 7.3, the fixed statement operates on additional types beyond arrays, strings, fixed-size buffers, or unmanaged variables. Any type that implements a method named GetPinnableReference can be pinned. The GetPinnableReference must return a ref variable of an unmanaged type.

Let's consider the following simple class which contains the implementation of the GetPinnableReference method.

public class MyData
{
    private readonly int _value;

    public MyData(int value)
    {
        _value = value;
    }

    public ref readonly int GetPinnableReference()
    {
        return ref _value;
    }        
}

For the type to implement the fixed pattern, it must declare a parameterless method GetPinnableReference that returns the value of the unmanaged type by reference or read-only reference.

Now you can pin the above class in the fixed statement as shown below.

var myData = new MyData(39);
fixed (int* ptr = myData)
{
    Console.WriteLine(*ptr); 
}

The .NET types Span<T> and ReadOnlySpan<T> introduced in .NET Core 2.0 make use of this pattern and can be pinned as shown below.

int[] PascalsTriangle = {
              1,
            1,  1,
          1,  2,  1,
        1,  3,  3,  1,
      1,  4,  6,  4,  1,
    1,  5,  10, 10, 5,  1
};

Span<int> RowFive = new Span<int>(PascalsTriangle, 10, 5);

fixed (int* ptrToRow = RowFive)
{
    var sum = 0;
    for (int i = 0; i < RowFive.Length; i++)
    {
        sum += *(ptrToRow + i);
    }
    Console.WriteLine(sum);
}

You can also initialize multiple pointers of the same type in one statement as shown below.

var myData1 = new MyData(100);
var myData2 = new MyData(200);

fixed (int* ptr1 = myData1, ptr2 = myData2)
{
    Console.WriteLine(*ptr1);
    Console.WriteLine(*ptr2);
}

After the code in the statement is executed, any pinned variables are unpinned and subject to garbage collection. Therefore, do not point to those variables outside the fixed statement. The variables declared in the fixed statement are scoped to that statement, making this easier.

Pointers that are initialized in fixed statements are readonly variables, and you can not modify them. If you want to modify the pointer value, you must declare a second pointer, and modify that as shown below.

var myData = new MyData(72);
fixed (int* ptr = myData)
{
    Console.WriteLine(*ptr);

    int* newPtr = ptr;
    newPtr = newPtr + 10;
    Console.WriteLine(*newPtr);

    //ptr = ptr + 10;            //Error
}

Now, if you try to modify the variable declared in a fixed statement, you will get the following error.

Error CS1656 Cannot assign to 'ptr' because it is a 'fixed variable'