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
174: The stackalloc Keyword
You've likely written a method that needs a small, temporary workspace to hold some data before returning a result. Maybe you're parsing a small string, calculating a checksum, or managing a tiny window of values. The instinct for most of us is to just throw a quick array in there: var buffer = new int[16];. It feels harmless. It's only 16 integers, right?
The Cost of Constant Heap Allocations
public int CalculateLocalSum(int[] data)
{
// Naive approach: allocating on the heap every single call
int[] tempBuffer = new int[16];
for (int i = 0; i < data.Length && i < 16; i++)
{
tempBuffer[i] = data[i] * 2;
}
return tempBuffer.Sum();
}
The problem isn't the amount of memory; it's where that memory lives. Every time you use the new keyword for an array, you're allocating memory on the managed heap. If this method is part of a "hot path"—say, it's called 100,000 times a second in a game loop or a high-frequency trading app—you are creating 100,000 short-lived objects. Even though these objects are tiny, the Garbage Collector (GC) still has to track them and eventually clean them up. This leads to "GC pressure," where your application might stutter because the GC is working overtime to collect thousands of tiny, identical buffers.
Bypassing the Garbage Collector
This is where stackalloc comes in. It allows you to allocate a block of memory on the stack instead of the heap. Stack memory is automatically reclaimed the moment the method returns, meaning the GC never even knows the memory existed. There's no cleanup phase because the memory is just "popped" off the stack.
public int CalculateLocalSum(int[] data)
{
// Better approach: allocating on the stack
Span<int> tempBuffer = stackalloc int[16];
for (int i = 0; i < data.Length && i < 16; i++)
{
tempBuffer[i] = data[i] * 2;
}
int sum = 0;
foreach (var val in tempBuffer) sum += val;
return sum;
}
In the past, stackalloc required the unsafe keyword because it returned a raw pointer. But thanks to Span<T>, we can now use it in safe code. I generally prefer this pattern whenever I have a known, small upper bound for a temporary buffer. You get the performance of a raw pointer with the safety of a managed span.
The Danger of the Stack Limit
Now, I have to give you a warning: don't get carried away. The heap is massive, but the stack is tiny (usually only 1MB per thread). If you try to stackalloc a massive array, or if you do it inside a recursive method that calls itself thousands of times, you'll trigger a StackOverflowException. Unlike a OutOfMemoryException, a stack overflow is fatal—you can't catch it in a try-catch block; the process just dies.
As a rule of thumb, if you need more than a few kilobytes, stick to the heap. If you aren't sure how large the buffer needs to be at compile time, you can use a hybrid approach: stackalloc for small sizes and ArrayPool<T> for larger ones. It's a bit more boilerplate, but it's how we write high-performance C# that doesn't crash in production.
📋 Practical Task
Refactoring a High-Frequency Signal Smoothing Buffer
You are working on a digital signal processing module. There is a method called SmoothSignal that is called thousands of times per second. Currently, it allocates a temporary double[] array on every call to store a small window of 8 samples, causing significant GC spikes in the telemetry.
Your Task:
- Modify the
SmoothSignalmethod to replace the heap-allocated array (new double[8]) with a stack-allocated buffer usingstackalloc. - Ensure the buffer is wrapped in a
Span<double>to keep the code safe. - Verify that the logic still correctly sums the window and returns the average.
public class SignalProcessor
{
public double SmoothSignal(double[] incomingData)
{
// TODO: Replace this heap allocation with stackalloc
double[] window = new double[8];
for (int i = 0; i < 8 && i < incomingData.Length; i++)
{
window[i] = incomingData[i];
}
double sum = 0;
foreach (var val in window)
{
sum += val;
}
return sum / 8;
}
}
There are no comments for now.