Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

Fixed pattern – C# 7.3 in Rider and ReSharper

The latest versions of ReSharper 2018.2 and Rider 2018.2 come with support for C# 7.3. In this series, we are looking at these new language features in C# 7.3. Today we will look at the fixed pattern and simplified access to fixed size buffer elements.

This post is part of a series:

Fixed statement

One of the common themes in C# 7.0 and the following point releases is to make it easier to write high-performance code. The feature we are going to cover today keeps this theme and is aimed to tackle the limitations of the fixed statement.

Let’s refresh our memory first and look at what the fixed statement does, and how to use it. Sometimes when interoperating with native code or when writing a performance-critical piece of code, we need to take a variable address and perform some operation on the pointer that stores the address. Because variables allocated on the managed heap can be moved during garbage collection, it’s required to fix them in order to use their addresses safely.

The fixed statement is intended exactly for this purpose, it “fixes” the movable variable so that its address remains constant for the duration of the statement. The example below demonstrates the use of the fixed statement:

void Example(int[] arr, string str, Span span)
{
    int arrSum = 0;

    // array is allocated on the managed heap, fixing required
    fixed (int* startPtr = arr)
    {
        // increment the sum while moving the pointer from the end to start of the array
        int* endPtr = startPtr + arr.Length;
        while (endPtr != startPtr)
          arrSum += *(--endPtr);
    }

    // string is allocated on managed heap, fixing required
    fixed (char* ptr = str)
    {
        // pass the pointer and the length into native code function
        var hash = NativeMethods.HashStringFast(ptr, str.Length);
    }

    // taking address of the variable on the stack doesn't require fixing
    int* sumAddress = &arrSum;

    // error: cannot take the address of, get the size of, or declare a pointer to a managed type
    string* strPtr = &str;

    // error before C# 7.3: the given expression cannot be used in a 'fixed' statement
    fixed (int* ptr = span)
    {
        Console.WriteLine(ptr[0]);
    } 
}

Fixed pattern

Before C# 7.3, the fixed statement was able to work only with arrays, System.String, fixed-size buffers, or unmanaged variables. The knowledge of how to obtain the pointer to underlying data was hardcoded in the compiler.

This limitation makes using certain types (like Span<T>, ReadOnlySpan<T> or ImmutableArray<T>), which would naturally fit the fixed statement, either impossible, or unpleasant because of having to resort to the System.Runtime.CompilerServices.Unsafe API. Fortunately C# 7.3 extended the set of types valid for the fixed statement by introducing the fixed pattern.

Patterns are used in C# all over the place: the foreach statement, LINQ queries, await expression – all these language constructs rely on the shape of the type, i.e. their set of available instance or extension methods and properties. For example, if the type has a method GetEnumerator and its return type declares method MoveNext and property Current, then the type can be used in a foreach statement. Neither IEnumerable<T> nor IEnumerator<T> are demanded by the compiler.

In order for the type to implement the fixed pattern, it should declare a parameterless method GetPinnableReference that returns the value of the unmanaged type by reference (or read-only reference). The following code snippet shows and example of pattern-based fixed statement.

Note: read more about unmanaged types in our previous post about new C# 7.3 type constraints.

public sealed class MyFixableType
{
    private readonly int _value;
    public MyFixableType(int value) => _value = value;
    public ref readonly int GetPinnableReference() => ref _value;
}

public unsafe class Test
{
    public static void Main()
    {
        var fixableObject = new MyFixableType(42);

        fixed (int* ptr = fixableObject)
        {
            Console.WriteLine(*ptr); // prints '42'
        }

        // in .NET Core 2.1 Span<T> and ReadOnlySpan<T> implement the 'fixed' pattern
        var span = "JetBrains".AsSpan();
        fixed (char* ptr = span)
        {
            Console.WriteLine(ptr[0]); // prints 'J'
        }
    } 
}

ReSharper and Rider come with a code inspection and quick-fix that allows to automatically generate the GetPinnableReference method from usage. Moreover, if the GetPinnableReference method already exists but its signature doesn’t match the expected one, another quick-fix is available to correct the method declaration.

Create 'GetPinnableReference' quick-fix

Simplified access to fixed size buffer elements

Let’s cover one more change introduced in C# 7.3: indexing fixed size buffer elements. Fixed size buffer fields are struct members available in unsafe contexts that represent C style in-line arrays. Such fields are rare beasts and primarily used for interoperation with native code. Let’s take a look at how they are declared.

public unsafe struct MyBufferWrapper
{
    // note: size is specified after field name, not after type name as in usual array declarations
    public fixed byte Buffer[4];

    // roughly equivalent to declaring four separate 'byte' fields:
    //
    // public byte Buffer_0;
    // public byte Buffer_1;
    // public byte Buffer_2;
    // public byte Buffer_3;
}

Although the  field declaration above uses the same fixed keyword as in fixed statement, here it has a completely different meaning and expresses the constant length of the in-line array rather than constant address.

Before C# 7.3 accessing the elements of a fixed size buffer which can possibly reside in memory movable by GC was allowed only after pinning the buffer with the fixed statement, while fixed size buffers guaranteed to be located in non-movable memory could be indexed directly.

public unsafe struct MyBufferWrapper
{
    public fixed byte Buffer[4];

    public int Foo() => Buffer[0] + Buffer[1] + Buffer[2] + Buffer[3]; // error before C# 7.3

    public int Bar()
    {
        fixed (byte* ptr = Buffer)
        {
            return ptr[0] + ptr[1] + ptr[2] + ptr[3]; // ok
        }
    }
}

The requirement to introduce auxiliary fixed pointer declaration is unjustified for cases when fixed size buffer is only used to access its elements, because unless address of the buffer is stashed somewhere indexing is always safe.

Luckily C# 7.3 removed the unneeded limitation for indexing movable fixed size buffers and made their use more natural:

public unsafe struct MyBufferWrapper
{
    public fixed byte Buffer[4];

    public int Foo() => Buffer[0] + Buffer[1] + Buffer[2] + Buffer[3]; // ok since C# 7.3

    public int Bar()
    {
        byte* ptr = Buffer; // error: taking address of the fixed size buffer still requires pinning
        
        return ptr[0] + ptr[1] + ptr[2] + ptr[3];
    }
}

Sure enough, ReSharper and Rider detect places where the use of the fixed statement isn’t required and provide a convenient quick-fix to remove redundant fixed pointer declaration. Needless to say, that quick-fix can be applied globally in the current file, project, or our whole solution.

Remove redundant fixed pointer declaration quick-fix

It’s interesting to see C# not only evolve towards powerful abstractions and more expressive language constructs, but also towards supporting interoperability and writing high-performance code.

Download ReSharper 2018.2 now! Or give Rider 2018.2 a try. We’d love to hear your thoughts!

image description