Writing unsafe .NET code without the unsafe keyword

 
 
  • Gérald Barré

Someone asked me why I set AllowUnsafeBlocks to true in the Meziantou.DotNet.CodingStandard package (source). This post explains what this property enables and why enabling it is not inherently unsafe.

#The AllowUnsafeBlocks property

The AllowUnsafeBlocks compiler option controls whether code that uses unsafe features can be compiled. It defaults to false. To enable unsafe code, add it to your project file:

csproj (MSBuild project file)
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

Enabling unsafe code allows two things in C#:

  • Use of the unsafe keyword, which allows pointers, fixed-size buffers, and the fixed keyword.

    C#
    // The unsafable keyword is required to use pointers (int*).
    unsafe static void Square(int* p)
    {
        *p = *p * *p;
    }
  • Use of the [SkipLocalsInit] attribute, which instructs the compiler to skip the initialization of local variables. I've written about this attribute in a previous article.

    C#
    [System.Runtime.CompilerServices.SkipLocalsInit]
    static unsafe void DemoZeroing()
    {
        int i;
        Console.WriteLine(*&i); // Unpredictable output as i is not initialized
    }

#Writing unsafe code without the unsafe keyword

The System.Runtime.CompilerServices.Unsafe class, introduced in .NET Core 2.0, provides methods that enable truly unsafe operations without the unsafe keyword. You do not need AllowUnsafeBlocks set to true to use it.

It provides pointer-like features, such as pointer arithmetic on references:

C#
{
    int foo = 0;
    int bar = 0;

    // Get a reference to foo using a ref to bar
    ref var value = ref Unsafe.Add(ref bar, 1);

    // Edit foo
    value = 42;

    // Print "foo=42"
    Console.WriteLine($"foo={foo}");
}

You can get AccessViolationException when accessing unallocated pages:

C#
{
   int a = 0;
   ref var value = ref Unsafe.Add(ref a, 4096);

   // System.AccessViolationException: Attempted to read or write protected memory.
   // This is often an indication that other memory is corrupt.
   value = 42;
}

You can change the type of an instance:

C#
var a = new Vehicule() { Name = "test" };

var b = Unsafe.As<Printable>(a);
b.Print(); // Prints "test"


class Vehicule
{
    public string Name { get; set; }
}

class Printable
{
    public string Name { get; set; }

    public void Print() { Console.WriteLine(Name); }
}

You can use uninitialized variables:

C#
Unsafe.SkipInit(out SampleStruct a);
Console.WriteLine(a.A); // Undefined value
Console.WriteLine(a.B); // Undefined value

struct SampleStruct
{
    public int A;
    public int B;
}

Notably, you can set the value of a variable before declaring it:

C#
var dummy = 0;
ref var value = ref Unsafe.Add(ref dummy, -4);
value = 42; // Set the value of "a" which is declared here-after

Unsafe.SkipInit(out int a);
Console.WriteLine(a); // 42

Another way to perform unsafe operations without the unsafe keyword is to use P/Invoke to call native methods:

C#
[DllImport("my.dll")]
static extern void MethodDoingUnsafeThingsWithMemory(IntPtr point);

#Conclusion

You don't need the unsafe keyword to write unsafe code, as the Unsafe class demonstrates. Setting AllowUnsafeBlocks to true is no less safe than leaving it at false; it simply unlocks the unsafe keyword for when you need it.

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?