C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
167: The unsafe and fixed Keywords
Imagine you're working in a high-end art gallery. Normally, you don't touch the paintings yourself. If you want to see a piece, you ask the curator, and they bring it to you or guide you to it. The curator handles all the logistics—they might move a painting from the east wing to the west wing overnight to make room for a new exhibit, but as long as you just ask for "The Starry Night," the curator finds it for you regardless of where it actually sits in the building. You don't need to know the GPS coordinates of the canvas; you just need the name.
But let's say you're a master restoration expert. To fix a microscopic tear in the canvas, you can't just "ask" for the painting. You need to go into the vault, put the painting on a table, and put your needle exactly three millimeters from the left edge. Now, here is the danger: if the curator decides to move that painting to a different table while your needle is mid-stroke, you're going to poke a hole right through the wall. To prevent this, you have to "pin" the painting to the table, telling the curators, "Do not move this piece until I am finished."
In C#, the "curator" is the Common Language Runtime (CLR) and its Garbage Collector (GC). Usually, you deal with references, not addresses. The GC is constantly moving objects around in memory to defragment it. The unsafe keyword is you deciding to be the restoration expert, and the fixed keyword is you pinning the painting to the table.
Breaking into the Memory Vault
By default, C# is a "safe" language. This means the runtime prevents you from accessing memory addresses directly, which stops you from accidentally overwriting critical system data or crashing the app with a segmentation fault. When you mark a block of code as unsafe, you're telling the compiler, "I know what I'm doing; let me use pointers."
Pointers are essentially variables that hold the memory address of another value. I'll be honest with you: in 99% of your professional career, you won't need this. But when you're doing high-performance graphics, interfacing with old C++ DLLs, or manipulating massive buffers of raw bytes, pointers are significantly faster because they bypass the bounds-checking that C# normally does on every array access.
// Note: You must enable "Allow unsafe code" in your project properties to run this.
unsafe
{
int myValue = 10;
int* p = &myValue; // 'p' now holds the memory address of 'myValue'
Console.WriteLine($"Value: {myValue}");
Console.WriteLine($"Address: {(long)p:X}");
Console.WriteLine($"Value via pointer: {*p}"); // Dereferencing the pointer
}
Stopping the Garbage Collector from Moving the Furniture
Now, here is where things get tricky. If you take a pointer to a managed object (like an array), you have a problem. The Garbage Collector might decide to move that array to a different part of the heap to optimize memory while your pointer is still pointing to the old location. Your pointer becomes "dangling," and the next time you use it, you're reading random garbage or crashing the program.
The fixed statement solves this. It "pins" the object in place, telling the GC: "Leave this specific object exactly where it is until this block of code finishes."
Let's look at a real-world scenario: imagine we have a buffer of bytes and we want to XOR them for a very basic encryption. Using a for loop with indexers is fine, but using pointers is the "metal" way to do it.
public unsafe void ScrambleBuffer(byte[] buffer, byte key)
{
// We 'fix' the buffer so the GC doesn't move it while we're pointer-walking
fixed (byte* pStart = buffer)
{
byte* pCurrent = pStart;
for (int i = 0; i < buffer.Length; i++)
{
// Access the value at the current address, XOR it, and move to the next byte
*pCurrent = (byte)(*pCurrent ^ key);
pCurrent++; // Pointer arithmetic: move to the next memory address
}
}
// Once we exit the fixed block, the GC is free to move the buffer again.
}
Notice how pCurrent++ works. In a normal array, you'd do i++ and then access buffer[i]. With pointers, you are literally sliding across the memory chip, one byte at a time. It's elegant, it's fast, and if you mess up the math, it's catastrophic. That's why we keep it wrapped in those specific keywords.
📋 Practical Task
Building a Fast-Byte Summation Tool
Your task is to create a method that calculates the total sum of all bytes in a large array using unsafe and fixed keywords. This is a common pattern in checksum calculations where performance is critical.
Requirements:
- Create a method
public unsafe long SumBytes(byte[] data). - Use a
fixedblock to obtain a pointer to the start of the array. - Use a pointer (not an indexer like
data[i]) to iterate through the entire array. - Accumulate the values into a
longvariable to prevent overflow. - Return the final sum.
Testing your code:
Initialize a byte array with 1,000,000 elements (you can fill them with random values or a constant) and verify that your pointer-based sum matches the result of a standard LINQ .Sum() call.
There are no comments for now.