C# for System Programming: When High-Level Meets Low-Level Hardware
\n\nWhen you hear the phrase "system programming," images of bearded gurus writing in C or assembly, with terminals filled with green text, usually come to mind. And C# — that's for "business applications," "Windows Forms," and "for those who don't want to bother with pointers." Spoiler: that's a myth.
\n\nIn reality, C# is a Swiss Army knife for the system programmer who wants to write quickly, safely, but when necessary — dive into the thick of unmanaged memory. In this article, we'll break down how C# helps solve real system tasks: from working with processes to direct memory access via WinAPI. And yes, there will be code examples — can't do without them.
Why C# Is Not Just a "Business Toy"?
\n\nSystem programming in C# is not an oxymoron. The .NET platform (especially .NET 5+) gives the developer a unique combination: performance close to native code and the convenience of a high-level language. You can write drivers, work with processes, allocate memory manually, and not go crazy managing resources.
\n\nKey features that make C# suitable for system programming:
\n- \n
- Unsafe code and pointers — yes, in C# you can work with addresses directly, like in C++. \n
- P/Invoke — calling any functions from native libraries (WinAPI, libc, drivers). \n
- Span
and Memory — safe work with contiguous memory blocks without unnecessary allocations. \n - Native AOT — compilation to native code without JIT and CLR. \n
- Interop with C/C++ — embedding C# into existing system projects. \n
If you thought C# was only about web and databases, get ready to be surprised.
\n\nUnsafe Code: Playing with Fire, But Under Control
\n\nThe unsafe keyword in C# is like a red button on a nuclear missile. Press it — and you can do everything: dereference pointers, work with fixed objects, allocate stack memory. But the responsibility is absolute.
Let's say we need to quickly copy a byte array, bypassing bounds checking. In safe code, we would use Array.Copy, but if we are writing a system utility to work with a driver, every microsecond counts.
using System;\n\nunsafe class MemoryCopy\n{\n public static void FastCopy(byte[] source, byte[] dest, int length)\n {\n fixed (byte* pSrc = source, pDst = dest)\n {\n byte* ps = pSrc;\n byte* pd = pDst;\n for (int i = 0; i < length; i++)\n *pd++ = *ps++;\n }\n }\n}\n\nHere we use fixed to "pin" the managed arrays in memory so the garbage collector doesn't move them during copying. The result is copying at speeds close to memcpy in C. Of course, for everyday tasks this is overkill, but for system programming in C# — it's a must-have.
When is unsafe justified?
\n- \n
- Working with network packets at the raw byte level. \n
- Interacting with hardware via DMA. \n
- Implementing custom memory allocators. \n
- Optimizing critical code sections (e.g., processing video from a camera). \n
P/Invoke and WinAPI: C# Knocks on the Kernel
\n\nReal system programming is impossible without calling native functions. C# provides the P/Invoke (Platform Invocation Services) mechanism for this. Want to get a list of processes, manage windows, or read another process's memory? No problem.
\n\nLet's look at a classic example — getting a list of running processes via the WinAPI function CreateToolhelp32Snapshot.
using System;\nu