Advanced C# Tips: Don’t Use unsafe for Minor Gains

Advanced C# Tips: Don’t Use unsafe for Minor Gains

The title could have also been “Don’t use unsafe code at all!” You will come to the same conclusion after reading this post, but still I gave a room for its usage. In C#, the usage of unsafe code is a topic of debate. On one side, it offers the potential for performance gains in specific scenarios. Yet the risks and complexities it introduces often outweigh its benefits.

What is unsafe

Unsafe code in C# refers to a block of code that uses pointers and allows direct memory manipulation, bypassing the .NET runtime’s type safety and security checks. This capability is powerful, but it comes with considerable risks. It allows developers to step outside the managed environment of the .NET runtime. It offers control over memory operations. However, it comes with a cost. My opinion about this debate would be this: if you are working on an application which can be solved in C# or its closest equivalent Java, you shouldn’t need memory level manipulations. If you need re-assess your requirements and design. If you really believe that you need unsafe, you may consider using a different language (like C, C++, D, Rust etc.) which naturally supports memory level manipulations.

In C#, unsafe code specifically refers to sections of code marked with the unsafe keyword. This keyword allows the use of pointers. For readers who are not familiar with pointers, a pointer is a variable that holds the memory address of another variable. So, the pointer allows direct access to the memory and also allows ability to manipulate the data in memory.

In system-level languages like C and C++, pointers are first class citizens of the language. They are natural parts in the toolbox and are commonly used for various types of problems. However, in languages like C# and Java, direct hardware access is abstracted with another layer on top of the operating system. For Java it is called Java Virtual Machine (JVM) for example, and for C# it is Common Language Runtime (CLR). These runtimes undertake the memory management and doesn’t let developer to do this since manipulating them may cause significant problems. Before going into the details of why we should avoid, I want to show usage of unsafe in C# and talk about use cases that unsafe is used.

In this example, we use pointer manipulation to access the memory address of a variable. This approach bypasses the type safety and memory safety provided by the .NET runtime.

unsafe {
    int var = 5;
    int* ptr = &var;
    Console.WriteLine("Value of var: {0}", var);
    Console.WriteLine("Address of var: {0}", (long)ptr);
}

Some commonly used cases where unsafe code is considered are listed below. Generally, they are performance critical problems.

  • High-Performance Computing: Applications that require intense mathematical calculations, like scientific simulations, might benefit from unsafe code.
  • Interoperability: When interfacing with low-level system APIs or hardware, unsafe code might be necessary for direct memory access.
  • Image Processing: Operations on large image data sets where performance is crucial might warrant the use of unsafe code.

Why we should avoid unsafe?

Why using unsafe code is being described such a sin? By using unsafe code, developers bypass critical safety features of the .NET runtime. The most common concerns are as follows:

1. Type Safety: In safe code, the .NET CLR enforces a strict type checking. In other words, the type of every object is known and checked at runtime. It prevents operations that are not safe for that particular type. In unsafe code however, this well-organized type checking is bypassed. Developers can perform operations that the runtime would normally prohibit due to type incompatibility. It has the risk of errors such as type mismatches which can lead to unpredictable behaviour or application crashes.

2. Memory Safety: The .NET CLR manages memory automatically through garbage collection. It allocates and deallocates memory, ensuring that objects no longer in use are properly cleaned up. This greatly reduces the likelihood of memory leaks and memory corruption. In unsafe code, developers have direct control over memory allocation and deallocation. This manual management can lead to issues like memory leaks and memory corruption, both of which can cause application instability and data loss.

3. Security: The managed environment of the .NET, CLR provides a layer of protection against various security vulnerabilities. For instance, it prevents operations that could write data to arbitrary memory locations, a common exploit in security attacks. However, the direct memory access provided by unsafe code can expose applications to security risks, such as buffer overflow attacks. In these attacks, an attacker could exploit the ability to write beyond the bounds of allocated memory to inject malicious code or access sensitive data.

Of course the reasons for not using unsafe code are not limited to the ones above.

To sum up, the risks associated with manual memory management, type safety violations, and security vulnerabilities need to be thought twice before using. They are far outweigh for the minor performance benefits. Developers should leverage the advanced features and optimizations provided by the .NET runtime. Even though I don’t advice, developers should only use unsafe code when it is unavoidable necessary. If developers prefer doing so, they ensure that the applications they build are secure, maintainable, and as portable as possible.

See Microsoft’s reference for unsafe here.

Suleyman Cabir Ataman, PhD

Sharing on social media:

Leave a Reply