Skip to content

/flags/csharp/dotnet-build-allowunsafeblocks

C# AllowUnsafeBlocks (-unsafe): fix CS0227 "Unsafe code may only appear if compiling with /unsafe"

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · 10.0.401updated
tool
C#
command
dotnet build
flag
-p:AllowUnsafeBlocks=true (<AllowUnsafeBlocks> in the .csproj, csc -unsafe)
default
false: the unsafe keyword is an error

<AllowUnsafeBlocks>true</AllowUnsafeBlocks> in the project file, or dotnet build -p:AllowUnsafeBlocks=true, lets C# code use the unsafe keyword: pointers, fixed statements, pointer arithmetic, stackalloc into a pointer and function pointers. Without it, any unsafe block, method or type fails with error CS0227: Unsafe code may only appear if compiling with /unsafe. The property maps to the compiler option -unsafe (/unsafe).

Without and with the property

bash
dotnet new console -n App -o . >/dev/null 2>&1
cat > Program.cs <<'EOF'
int[] data = [10, 20, 30];
unsafe
{
    fixed (int* p = data)
    {
        Console.WriteLine(*(p + 2));
    }
}
EOF
build() { dotnet build --no-incremental "$@" 2>&1 | grep -oE "(warning|error) CS[0-9]+[^[]*" | sort -u; echo "exit: ${PIPESTATUS[0]}"; }
echo "== default"
build
echo "== -p:AllowUnsafeBlocks=true"
build -p:AllowUnsafeBlocks=true
dotnet run --no-build

Output

== default
error CS0227: Unsafe code may only appear if compiling with /unsafe
exit: 1
== -p:AllowUnsafeBlocks=true
exit: 0
30

The default build stops at CS0227. With the property set, the same code compiles and prints the third element, read through a pointer that fixed pins to the array.

What needs it

CodeNeeds AllowUnsafeBlocks
unsafe { ... } blocks, unsafe methods, unsafe struct/classyes
Pointer types: int*, byte*, void*yes
fixed (byte* p = array) and fixed-size buffers (fixed int buf[8])yes
int* p = stackalloc int[16]yes
Function pointers: delegate* unmanaged<int, int>yes
Span<int> s = stackalloc int[16]no, safe code
Span<T>, MemoryMarshal, Unsafe class (System.Runtime.CompilerServices)no
[DllImport] / [LibraryImport] with normal parametersno, unless the signature uses pointers
[LibraryImport] source-generated marshallingyes in many cases: the generated code uses pointers

Setting it

App.csproj
<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Set it only in the projects that contain unsafe code, not repository-wide in Directory.Build.props, so a code review notices when unsafe code appears in a new project. For a quick experiment, dotnet build -p:AllowUnsafeBlocks=true works without editing the file (add --no-incremental if a previous build already succeeded with other settings).

Pitfalls

  • Pointers into managed objects are only valid inside fixed (or with a pinned GCHandle); storing such a pointer and using it later reads memory the garbage collector may have moved.
  • The runtime does not check pointer arithmetic: reading past the end of the array is undefined behaviour, not an IndexOutOfRangeException.
  • CS0214 (Pointers and fixed size buffers may only be used in an unsafe context) is a different error: the project allows unsafe code, but the statement is outside an unsafe block or method.
  • Language features also depend on the C# version: function pointers need C# 9 or later, see LangVersion.

FAQ

Does AllowUnsafeBlocks make my whole program unsafe?

No. It only allows the unsafe keyword. Code outside unsafe blocks, methods and types is compiled and verified exactly as before; only the marked regions may use pointers and other unverifiable constructs.

Do I need unsafe code for stackalloc or Span<T>?

No. Span<int> s = stackalloc int[16]; is safe code and needs no AllowUnsafeBlocks. You need unsafe only when you take raw pointers, for example int* p = stackalloc int[16]; or fixed (byte* p = buffer).

Is unsafe code faster?

Sometimes, in tight loops that avoid bounds checks, but Span<T>, Memory<T> and System.Runtime.InteropServices.MemoryMarshal usually reach the same speed without pointers. Measure before switching, and keep unsafe regions small.

Sources