Writing unsafe .NET code without the unsafe keyword
Someone asked me why I set AllowUnsafeBlocks
to true
in the Meziantou.DotNet.CodingStandard
package (source). In this post, I'll explain what this property enables, and why it is not more unsafe to set it to true
.
#The AllowUnsafeBlocks
property
The AllowUnsafeBlocks
compiler option allows code that uses unsafe features to compile. The default value for this option is false
, meaning unsafe code is not allowed. To enable unsafe code, you must add the AllowUnsafeBlocks
compiler option to your project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
Enabling unsafe code allows 2 things in C#:
Allow the usage of the
unsafe
keyword. This keyword allows the usage of pointers, fixed-size buffers, and thefixed
keyword.C#// The unsafable keyword is required to use pointers (int*). unsafe static void Square(int* p) { *p = *p * *p; }
The use of the
[SkipLocalsInit]
attribute which instructs the compiler to skip the initialization of local variables. I've already 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
contains many methods that allow writing unsafe code, without the unsafe
keyword. So, you don't need to set AllowUnsafeBlocks
to true
to use this class. This class was introduced with .NET Core 2.0 and allow to do really unsafe code.
You can use it to get pointer-like features. For instance, you can do pointer arithmetic from references:
{
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:
{
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:
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:
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;
}
BTW, you can set the value of a variable before declaring it:
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 do unsafe things without using the unsafe
keyword is to use interop to call native methods and execute any unsafe code:
[DllImport("my.dll")]
static extern void MethodDoingUnsafeThingsWithMemory(IntPtr point);
#Conclusion
You need to explicitly use the unsafe
keyword to write unsafe code. Also, you can write unsafe code without the unsafe
keyword. So, setting AllowUnsafeBlocks
to true
is not more unsafe than setting it to false
. It just allows you to use the unsafe
keyword when you want to.
#Additional resources
Do you have a question or a suggestion about this post? Contact me!