What is Unsafe Code?
Unsafe code in C# allows you to work directly with pointers and unmanaged memory, bypassing .NET’s memory safety guarantees. It enables operations like pointer arithmetic, direct memory access, and calling unmanaged functions. While it sacrifices safety for performance, it’s essential for scenarios requiring maximum speed, interop with native code, or low-level memory manipulation.
How to Use It
Enable unsafe code in your project settings and use the unsafe
keyword on methods, classes, or code blocks. Use fixed statements to pin managed memory, stackalloc for stack allocation, and pointers for direct memory access. Always validate your unsafe code thoroughly, as it can cause memory corruption and security vulnerabilities.
Example: High-Performance Memory Operations
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Enable unsafe code for the entire class
public unsafe class UnsafeMemoryOperations
{
// Fast memory copy using pointers - faster than Array.Copy for large arrays
public static void FastMemoryCopy(byte[] source, byte[] destination, int length)
{
// Validate input parameters
if (source == null || destination == null)
throw new ArgumentNullException();
if (length > source.Length || length > destination.Length)
throw new ArgumentOutOfRangeException();
// Pin both arrays in memory so GC won't move them
fixed (byte* srcPtr = source)
fixed (byte* destPtr = destination)
{
// Get pointers to the start of arrays
byte* src = srcPtr;
byte* dest = destPtr;
// Copy 8 bytes at a time for better performance
long* srcLong = (long*)src;
long* destLong = (long*)dest;
// Calculate how many 8-byte chunks we can copy
int longCount = length / sizeof(long);
// Copy 8 bytes at a time using long pointers
for (int i = 0; i < longCount; i++)
{
*destLong = *srcLong; // Copy 8 bytes in one operation
srcLong++; // Move to next 8-byte chunk
destLong++;
}
// Handle remaining bytes that don't fit in 8-byte chunks
int remainingBytes = length % sizeof(long);
if (remainingBytes > 0)
{
// Switch back to byte pointers for remainder
src = (byte*)srcLong;
dest = (byte*)destLong;
// Copy remaining bytes one by one
for (int i = 0; i < remainingBytes; i++)
{
*dest = *src;
src++;
dest++;
}
}
}
}
// Fast string comparison using pointers
public static bool FastStringEquals(string str1, string str2)
{
// Quick checks first
if (ReferenceEquals(str1, str2)) return true;
if (str1 == null || str2 == null) return false;
if (str1.Length != str2.Length) return false;
// Pin both strings for direct memory access
fixed (char* ptr1 = str1)
fixed (char* ptr2 = str2)
{
char* p1 = ptr1;
char* p2 = ptr2;
// Compare characters using pointer arithmetic
int length = str1.Length;
// Compare 4 chars (8 bytes) at a time on 64-bit systems
long* longPtr1 = (long*)p1;
long* longPtr2 = (long*)p2;
int longCount = length / 4; // 4 chars per long
// Fast comparison of 8-byte chunks
for (int i = 0; i < longCount; i++)
{
if (*longPtr1 != *longPtr2)
return false;
longPtr1++;
longPtr2++;
}
// Handle remaining characters
p1 = (char*)longPtr1;
p2 = (char*)longPtr2;
int remaining = length % 4;
for (int i = 0; i < remaining; i++)
{
if (*p1 != *p2)
return false;
p1++;
p2++;
}
return true;
}
}
// Stack allocation for temporary buffers - no heap allocation
public static int ProcessNumbers(ReadOnlySpan<int> numbers)
{
int count = numbers.Length;
// Allocate temporary buffer on stack - very fast, no GC pressure
int* tempBuffer = stackalloc int[count];
// Copy input data to stack buffer for processing
fixed (int* numbersPtr = numbers)
{
// Fast memory copy using Unsafe.CopyBlock
Unsafe.CopyBlock(tempBuffer, numbersPtr, (uint)(count * sizeof(int)));
}
// Process data in-place using pointer arithmetic
for (int i = 0; i < count; i++)
{
// Square each number
tempBuffer[i] = tempBuffer[i] * tempBuffer[i];
}
// Calculate sum using pointer iteration
int sum = 0;
int* ptr = tempBuffer;
int* end = tempBuffer + count;
// Loop using pointer comparison - very efficient
while (ptr < end)
{
sum += *ptr;
ptr++; // Move to next int
}
return sum;
// Stack allocated memory is automatically freed when method exits
}
// Unsafe struct for direct memory layout control
[StructLayout(LayoutKind.Explicit)]
public struct FloatIntUnion
{
// Both fields occupy the same memory location
[FieldOffset(0)]
public float FloatValue;
[FieldOffset(0)]
public int IntValue;
// Fast float-to-int bit conversion without actual conversion
public static int FloatToIntBits(float value)
{
FloatIntUnion union;
union.IntValue = 0; // Initialize to avoid compiler warning
union.FloatValue = value;
return union.IntValue;
}
public static float IntBitsToFloat(int value)
{
FloatIntUnion union;
union.FloatValue = 0f;
union.IntValue = value;
return union.FloatValue;
}
}
// High-performance hash calculation using unsafe code
public static uint FastHash(ReadOnlySpan<byte> data)
{
const uint FNV_PRIME = 16777619;
const uint FNV_OFFSET_BASIS = 2166136261;
uint hash = FNV_OFFSET_BASIS;
fixed (byte* dataPtr = data)
{
byte* ptr = dataPtr;
byte* end = dataPtr + data.Length;
// Process 4 bytes at a time when possible
uint* uintPtr = (uint*)ptr;
uint* uintEnd = (uint*)(end - 3); // Ensure we don't read past end
// Process 4-byte chunks
while (uintPtr < uintEnd)
{
hash ^= *uintPtr;
hash *= FNV_PRIME;
uintPtr++;
}
// Process remaining bytes
ptr = (byte*)uintPtr;
while (ptr < end)
{
hash ^= *ptr;
hash *= FNV_PRIME;
ptr++;
}
}
return hash;
}
}
// Usage examples
public class UnsafeCodeExample
{
public static void DemonstrateUnsafeOperations()
{
// Test fast memory copy
byte[] source = new byte[1000];
byte[] destination = new byte[1000];
// Fill source with test data
for (int i = 0; i < source.Length; i++)
{
source[i] = (byte)(i % 256);
}
// Fast copy using unsafe code
UnsafeMemoryOperations.FastMemoryCopy(source, destination, source.Length);
Console.WriteLine($"Copied {source.Length} bytes");
Console.WriteLine($"First few bytes: {destination[0]}, {destination[1]}, {destination[2]}");
// Test fast string comparison
string str1 = "Hello, World!";
string str2 = "Hello, World!";
string str3 = "Hello, Universe!";
bool equal1 = UnsafeMemoryOperations.FastStringEquals(str1, str2);
bool equal2 = UnsafeMemoryOperations.FastStringEquals(str1, str3);
Console.WriteLine($"'{str1}' == '{str2}': {equal1}");
Console.WriteLine($"'{str1}' == '{str3}': {equal2}");
// Test stack allocation and processing
int[] numbers = { 1, 2, 3, 4, 5 };
int result = UnsafeMemoryOperations.ProcessNumbers(numbers.AsSpan());
Console.WriteLine($"Sum of squares: {result}"); // 1 + 4 + 9 + 16 + 25 = 55
// Test union for bit manipulation
float pi = 3.14159f;
int piBits = UnsafeMemoryOperations.FloatIntUnion.FloatToIntBits(pi);
float piRestored = UnsafeMemoryOperations.FloatIntUnion.IntBitsToFloat(piBits);
Console.WriteLine($"Float: {pi}, Bits: {piBits:X8}, Restored: {piRestored}");
// Test fast hashing
byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello, unsafe world!");
uint hash = UnsafeMemoryOperations.FastHash(data);
Console.WriteLine($"Hash of '{System.Text.Encoding.UTF8.GetString(data)}': {hash:X8}");
}
}